Coverage Report


Files: 76
Lines: 14056
Covered: 2579 / 4148 (62.2%)


Lines covered: 67 / 116 (57.8%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
import "./Interfaces/IActivePool.sol";
6
import "./Interfaces/ICollSurplusPool.sol";
7
import "./Dependencies/ICollateralToken.sol";
8
import "./Interfaces/ICdpManagerData.sol";
9
import "./Dependencies/ERC3156FlashLender.sol";
10
import "./Dependencies/SafeERC20.sol";
11
import "./Dependencies/ReentrancyGuard.sol";
12
import "./Dependencies/AuthNoOwner.sol";
13
import "./Dependencies/BaseMath.sol";
14

                            
                        
15
/**
16
 * The Active Pool holds the collateral and EBTC debt (but not EBTC tokens) for all active cdps.
17
 *
18
 * When a cdp is liquidated, it's collateral and EBTC debt are transferred from the Active Pool, to either the
19
 * Stability Pool, the Default Pool, or both, depending on the liquidation conditions.
20
 */
21
contract ActivePool is IActivePool, ERC3156FlashLender, ReentrancyGuard, BaseMath, AuthNoOwner {
22
    using SafeERC20 for IERC20;
23
    string public constant NAME = "ActivePool";
24

                            
                        
25
    address public immutable borrowerOperationsAddress;
26
    address public immutable cdpManagerAddress;
27
    address public immutable collSurplusPoolAddress;
28
    address public feeRecipientAddress;
29

                            
                        
30
    uint256 internal systemCollShares; // deposited collateral tracker
31
    uint256 internal systemDebt;
32
    uint256 internal feeRecipientCollShares; // coll shares claimable by fee recipient
33
    ICollateralToken public collateral;
34

                            
                        
35
    // --- Contract setters ---
36

                            
                        
37
    /// @notice Constructor for the ActivePool contract
38
    /// @dev Initializes the contract with the borrowerOperationsAddress, cdpManagerAddress, collateral token address, collSurplusAddress, and feeRecipientAddress
39
    /// @param _borrowerOperationsAddress The address of the Borrower Operations contract
40
    /// @param _cdpManagerAddress The address of the Cdp Manager contract
41
    /// @param _collTokenAddress The address of the collateral token
42
    /// @param _collSurplusAddress The address of the collateral surplus pool
43
    /// @param _feeRecipientAddress The address of the fee recipient
44

                            
                        
45
    constructor(
46
        address _borrowerOperationsAddress,
47
        address _cdpManagerAddress,
48
        address _collTokenAddress,
49
        address _collSurplusAddress,
50
        address _feeRecipientAddress
51
    ) {
52
        borrowerOperationsAddress = _borrowerOperationsAddress;
53
        cdpManagerAddress = _cdpManagerAddress;
54
        collateral = ICollateralToken(_collTokenAddress);
55
        collSurplusPoolAddress = _collSurplusAddress;
56
        feeRecipientAddress = _feeRecipientAddress;
57

                            
                        
58
        // TEMP: read authority to avoid signature change
59
        address _authorityAddress = address(AuthNoOwner(cdpManagerAddress).authority());
60
        if (_authorityAddress != address(0)) {
61
            _initializeAuthority(_authorityAddress);
62
        }
63

                            
                        
64
        emit FeeRecipientAddressChanged(_feeRecipientAddress);
65
    }
66

                            
                        
67
    // --- Getters for public variables. Required by IPool interface ---
68

                            
                        
69
    /// @notice Amount of stETH collateral shares in the contract
70
    /// @dev Not necessarily equal to the the contract's raw systemCollShares balance - tokens can be forcibly sent to contracts
71
    /// @return uint256 The amount of systemCollShares allocated to the pool
72

                            
                        
73
    function getSystemCollShares() external view override returns (uint256) {
74
        return systemCollShares;
75
    }
76

                            
                        
77
    /// @notice Returns the systemDebt state variable
78
    /// @dev The amount of EBTC debt in the pool. Like systemCollShares, this is not necessarily equal to the contract's EBTC token balance - tokens can be forcibly sent to contracts
79
    /// @return uint256 The amount of EBTC debt in the pool
80

                            
                        
81
    function getSystemDebt() external view override returns (uint256) {
82
        return systemDebt;
83
    }
84

                            
                        
85
    /// @notice The amount of stETH collateral shares claimable by the fee recipient
86
    /// @return uint256 The amount of collateral shares claimable by the fee recipient
87

                            
                        
88
    function getFeeRecipientClaimableCollShares() external view override returns (uint256) {
89
        return feeRecipientCollShares;
90
    }
91

                            
                        
92
    // --- Pool functionality ---
93

                            
                        
94
    /// @notice Sends stETH collateral shares to a specified account
95
    /// @dev Only for use by system contracts, the caller must be either BorrowerOperations or CdpManager
96
    /// @param _account The address of the account to send stETH to
97
    /// @param _shares The amount of stETH shares to send
98

                            
                        
99
    function transferSystemCollShares(address _account, uint256 _shares) public override {
100
        _requireCallerIsBOorCdpM();
101

                            
                        
102
        uint256 cachedSystemCollShares = systemCollShares;
103
        require(cachedSystemCollShares >= _shares, "!ActivePoolBal");
104
        unchecked {
105
            // Can use unchecked due to above
106
            cachedSystemCollShares -= _shares; // Updating here avoids an SLOAD
107
        }
108

                            
                        
109
        systemCollShares = cachedSystemCollShares;
110

                            
                        
111
        emit SystemCollSharesUpdated(cachedSystemCollShares);
112
        emit CollSharesTransferred(_account, _shares);
113

                            
                        
114
        _transferCollSharesWithContractHooks(_account, _shares);
115
    }
116

                            
                        
117
    /// @notice Sends stETH to a specified account, drawing from both core shares and liquidator rewards shares
118
    /// @notice Liquidator reward shares are not tracked via internal accounting in the active pool and are assumed to be present in expected amount as part of the intended behavior of BorowerOperations and CdpManager
119
    /// @dev Liquidator reward shares are added when a cdp is opened, and removed when it is closed
120
    /// @dev closeCdp() or liqudations result in the actor (borrower or liquidator respectively) receiving the liquidator reward shares
121
    /// @dev Redemptions result in the shares being sent to the coll surplus pool for claiming by the CDP owner
122
    /// @dev Note that funds in the coll surplus pool, just like liquidator reward shares, are not tracked as part of the system CR or coll of a CDP.
123
    /// @dev Requires that the caller is either BorrowerOperations or CdpManager
124
    /// @param _account The address of the account to send systemCollShares and the liquidator reward to
125
    /// @param _shares The amount of systemCollShares to send
126
    /// @param _liquidatorRewardShares The amount of the liquidator reward shares to send
127

                            
                        
128
    function transferSystemCollSharesAndLiquidatorReward(
129
        address _account,
130
        uint256 _shares,
131
        uint256 _liquidatorRewardShares
132
    ) external override {
133
        _requireCallerIsBOorCdpM();
134

                            
                        
135
        uint256 cachedSystemCollShares = systemCollShares;
136
        require(cachedSystemCollShares >= _shares, "ActivePool: Insufficient collateral shares");
137
        uint256 totalShares = _shares + _liquidatorRewardShares; // TODO: Is this safe?
138
        unchecked {
139
            // Safe per the check above
140
            cachedSystemCollShares -= _shares;
141
        }
142
        systemCollShares = cachedSystemCollShares;
143

                            
                        
144
        emit SystemCollSharesUpdated(cachedSystemCollShares);
145
        emit CollSharesTransferred(_account, totalShares);
146

                            
                        
147
        _transferCollSharesWithContractHooks(_account, totalShares);
148
    }
149

                            
                        
150
    /// @notice Allocate stETH shares from the system to the fee recipient to claim at-will (pull model)
151
    /// @dev Requires that the caller is CdpManager
152
    /// @dev Only the current fee recipient address is able to claim the shares
153
    /// @dev If the fee recipient address is changed while outstanding claimable coll is available, only the new fee recipient will be able to claim the outstanding coll
154
    /// @param _shares The amount of systemCollShares to allocate to the fee recipient
155

                            
                        
156
    function allocateSystemCollSharesToFeeRecipient(uint256 _shares) external override {
157
        _requireCallerIsCdpManager();
158

                            
                        
159
        uint256 cachedSystemCollShares = systemCollShares;
160

                            
                        
161
        require(cachedSystemCollShares >= _shares, "ActivePool: Insufficient collateral shares");
162
        unchecked {
163
            // Safe per the check above
164
            cachedSystemCollShares -= _shares;
165
        }
166

                            
                        
167
        systemCollShares = cachedSystemCollShares;
168

                            
                        
169
        uint256 cachedFeeRecipientCollShares = feeRecipientCollShares + _shares;
170
        feeRecipientCollShares = cachedFeeRecipientCollShares;
171

                            
                        
172
        emit SystemCollSharesUpdated(cachedSystemCollShares);
173
        emit FeeRecipientClaimableCollSharesIncreased(cachedFeeRecipientCollShares, _shares);
174
    }
175

                            
                        
176
    /// @notice Helper function to transfer stETH shares to another address, ensuring to call hooks into other system pools if they are the recipient
177
    /// @param _account The address to transfer shares to
178
    /// @param _shares The amount of shares to transfer
179

                            
                        
180
    function _transferCollSharesWithContractHooks(address _account, uint256 _shares) internal {
181
        // NOTE: No need for safe transfer if the collateral asset is standard. Make sure this is the case!
182
        collateral.transferShares(_account, _shares);
183

                            
                        
184
        if (_account == collSurplusPoolAddress) {
185
            ICollSurplusPool(_account).increaseTotalSurplusCollShares(_shares);
186
        }
187
    }
188

                            
                        
189
    /// @notice Increases the tracked EBTC debt of the system by a specified amount
190
    /// @dev Managed by system contracts - requires that the caller is either BorrowerOperations or CdpManager
191
    /// @param _amount: The amount to increase the system EBTC debt by
192

                            
                        
193
    function increaseSystemDebt(uint256 _amount) external override {
194
        _requireCallerIsBOorCdpM();
195

                            
                        
196
        uint256 cachedSystemDebt = systemDebt + _amount;
197

                            
                        
198
        systemDebt = cachedSystemDebt;
199
        emit ActivePoolEBTCDebtUpdated(cachedSystemDebt);
200
    }
201

                            
                        
202
    /// @notice Decreases the tracked EBTC debt of the system by a specified amount
203
    /// @dev Managed by system contracts - requires that the caller is either BorrowerOperations or CdpManager
204
    /// @param _amount: The amount to decrease the system EBTC debt by
205

                            
                        
206
    function decreaseSystemDebt(uint256 _amount) external override {
207
        _requireCallerIsBOorCdpM();
208

                            
                        
209
        uint256 cachedSystemDebt = systemDebt - _amount;
210

                            
                        
211
        systemDebt = cachedSystemDebt;
212
        emit ActivePoolEBTCDebtUpdated(cachedSystemDebt);
213
    }
214

                            
                        
215
    // --- 'require' functions ---
216

                            
                        
217
    /// @notice Checks if the caller is BorrowerOperations
218
    function _requireCallerIsBorrowerOperations() internal view {
219
        require(
220
            msg.sender == borrowerOperationsAddress,
221
            "ActivePool: Caller is not BorrowerOperations"
222
        );
223
    }
224

                            
                        
225
    /// @notice Checks if the caller is either BorrowerOperations or CdpManager
226
    function _requireCallerIsBOorCdpM() internal view {
227
        require(
228
            msg.sender == borrowerOperationsAddress || msg.sender == cdpManagerAddress,
229
            "ActivePool: Caller is neither BorrowerOperations nor CdpManager"
230
        );
231
    }
232

                            
                        
233
    /// @notice Checks if the caller is CdpManager
234
    function _requireCallerIsCdpManager() internal view {
235
        require(msg.sender == cdpManagerAddress, "ActivePool: Caller is not CdpManager");
236
    }
237

                            
                        
238
    /// @notice Notify that stETH collateral shares have been recieved, updating internal accounting accordingly
239
    /// @param _value The amount of collateral to receive
240

                            
                        
241
    function increaseSystemCollShares(uint256 _value) external override {
242
        _requireCallerIsBorrowerOperations();
243

                            
                        
244
        uint256 cachedSystemCollShares = systemCollShares + _value;
245
        systemCollShares = cachedSystemCollShares;
246
        emit SystemCollSharesUpdated(cachedSystemCollShares);
247
    }
248

                            
                        
249
    // === Flashloans === //
250

                            
                        
251
    /// @notice Borrow assets with a flash loan
252
    /// @dev The Collateral checks may cause reverts if you trigger a fee change big enough
253
    ///         consider calling `cdpManagerAddress.syncGlobalAccountingAndGracePeriod()`
254
    /// @param receiver The address to receive the flash loan
255
    /// @param token The address of the token to loan
256
    /// @param amount The amount of tokens to loan
257
    /// @param data Additional data
258
    /// @return A boolean value indicating whether the operation was successful
259

                            
                        
260
    function flashLoan(
261
        IERC3156FlashBorrower receiver,
262
        address token,
263
        uint256 amount,
264
        bytes calldata data
265
    ) external override returns (bool) {
266
        require(amount > 0, "ActivePool: 0 Amount");
267
        uint256 fee = flashFee(token, amount); // NOTE: Check for `token` is implicit in the requires above // also checks for paused
268
        require(amount <= maxFlashLoan(token), "ActivePool: Too much");
269

                            
                        
270
        uint256 amountWithFee = amount + fee;
271
        uint256 oldRate = collateral.getPooledEthByShares(DECIMAL_PRECISION);
272

                            
                        
273
        collateral.transfer(address(receiver), amount);
274

                            
                        
275
        // Callback
276
        require(
277
            receiver.onFlashLoan(msg.sender, token, amount, fee, data) == FLASH_SUCCESS_VALUE,
278
            "ActivePool: IERC3156: Callback failed"
279
        );
280

                            
                        
281
        // Transfer of (principal + Fee) from flashloan receiver
282
        collateral.transferFrom(address(receiver), address(this), amountWithFee);
283

                            
                        
284
        // Send earned fee to designated recipient
285
        collateral.transfer(feeRecipientAddress, fee);
286

                            
                        
287
        // Check new balance
288
        // NOTE: Invariant Check, technically breaks CEI but I think we must use it
289
        // NOTE: This means any balance > systemCollShares is stuck, this is also present in LUSD as is
290

                            
                        
291
        // NOTE: This check effectively prevents running 2 FL at the same time
292
        //  You technically could, but you'd be having to repay any amount below systemCollShares to get Fl2 to not revert
293
        require(
294
            collateral.balanceOf(address(this)) >= collateral.getPooledEthByShares(systemCollShares),
295
            "ActivePool: Must repay Balance"
296
        );
297
        require(
298
            collateral.sharesOf(address(this)) >= systemCollShares,
299
            "ActivePool: Must repay Share"
300
        );
301
        require(
302
            collateral.getPooledEthByShares(DECIMAL_PRECISION) == oldRate,
303
            "ActivePool: Should keep same collateral share rate"
304
        );
305

                            
                        
306
        emit FlashLoanSuccess(address(receiver), token, amount, fee);
307

                            
                        
308
        return true;
309
    }
310

                            
                        
311
    /// @notice Calculate the flash loan fee for a given token and amount loaned
312
    /// @param token The address of the token to calculate the fee for
313
    /// @param amount The amount of tokens to calculate the fee for
314
    /// @return The amount of the flash loan fee
315

                            
                        
316
    function flashFee(address token, uint256 amount) public view override returns (uint256) {
317
        require(token == address(collateral), "ActivePool: collateral Only");
318
        require(!flashLoansPaused, "ActivePool: Flash Loans Paused");
319

                            
                        
320
        return (amount * feeBps) / MAX_BPS;
321
    }
322

                            
                        
323
    /// @notice Get the maximum flash loan amount for a specific token
324
    /// @dev Exclusively used here for stETH collateral, equal to the current balance of the pool
325
    /// @param token The address of the token to get the maximum flash loan amount for
326
    /// @return The maximum flash loan amount for the token
327
    function maxFlashLoan(address token) public view override returns (uint256) {
328
        if (token != address(collateral)) {
329
            return 0;
330
        }
331

                            
                        
332
        if (flashLoansPaused) {
333
            return 0;
334
        }
335

                            
                        
336
        return collateral.balanceOf(address(this));
337
    }
338

                            
                        
339
    // === Governed Functions === //
340

                            
                        
341
    /// @notice Claim outstanding shares for fee recipient, updating internal accounting and transferring the shares.
342
    /// @dev Call permissinos are managed via authority for flexibility, rather than gating call to just feeRecipient.
343
    /// @dev Is likely safe as an open permission though caution should be taken.
344
    /// @param _shares The amount of shares to claim to feeRecipient
345
    function claimFeeRecipientCollShares(uint256 _shares) external override requiresAuth {
346
        ICdpManagerData(cdpManagerAddress).syncGlobalAccountingAndGracePeriod(); // Calling this increases shares so do it first
347

                            
                        
348
        uint256 cachedFeeRecipientCollShares = feeRecipientCollShares;
349
        require(
350
            cachedFeeRecipientCollShares >= _shares,
351
            "ActivePool: Insufficient fee recipient coll"
352
        );
353

                            
                        
354
        unchecked {
355
            cachedFeeRecipientCollShares -= _shares;
356
        }
357

                            
                        
358
        feeRecipientCollShares = cachedFeeRecipientCollShares;
359
        emit FeeRecipientClaimableCollSharesDecreased(cachedFeeRecipientCollShares, _shares);
360

                            
                        
361
        collateral.transferShares(feeRecipientAddress, _shares);
362
    }
363

                            
                        
364
    /// @dev Function to move unintended dust that are not protected
365
    /// @notice moves given amount of given token (collateral is NOT allowed)
366
    /// @notice because recipient are fixed, this function is safe to be called by anyone
367

                            
                        
368
    function sweepToken(address token, uint256 amount) public nonReentrant requiresAuth {
369
        ICdpManagerData(cdpManagerAddress).syncGlobalAccountingAndGracePeriod(); // Accrue State First
370

                            
                        
371
        require(token != address(collateral), "ActivePool: Cannot Sweep Collateral");
372

                            
                        
373
        uint256 balance = IERC20(token).balanceOf(address(this));
374
        require(amount <= balance, "ActivePool: Attempt to sweep more than balance");
375

                            
                        
376
        address cachedFeeRecipientAddress = feeRecipientAddress; // Saves an SLOAD
377

                            
                        
378
        IERC20(token).safeTransfer(cachedFeeRecipientAddress, amount);
379

                            
                        
380
        emit SweepTokenSuccess(token, amount, cachedFeeRecipientAddress);
381
    }
382

                            
                        
383
    /// @notice Set new FeeRecipient
384
    /// @dev Previous fees are forfeited, if you wish to claim to previous address
385
    ///       call `claimFeeRecipientCollShares` first
386
    function setFeeRecipientAddress(address _feeRecipientAddress) external requiresAuth {
387
        ICdpManagerData(cdpManagerAddress).syncGlobalAccountingAndGracePeriod(); // Accrue State First
388

                            
                        
389
        require(
390
            _feeRecipientAddress != address(0),
391
            "ActivePool: Cannot set fee recipient to zero address"
392
        );
393

                            
                        
394
        feeRecipientAddress = _feeRecipientAddress;
395
        emit FeeRecipientAddressChanged(_feeRecipientAddress);
396
    }
397

                            
                        
398
    /// @notice Sets new Fee for FlashLoans
399
    function setFeeBps(uint256 _newFee) external requiresAuth {
400
        ICdpManagerData(cdpManagerAddress).syncGlobalAccountingAndGracePeriod(); // Accrue State First
401

                            
                        
402
        require(_newFee <= MAX_FEE_BPS, "ERC3156FlashLender: _newFee should <= MAX_FEE_BPS");
403

                            
                        
404
        // set new flash fee
405
        uint256 _oldFee = feeBps;
406
        feeBps = uint16(_newFee);
407
        emit FlashFeeSet(msg.sender, _oldFee, _newFee);
408
    }
409

                            
                        
410
    /// @notice Should Flashloans be paused?
411
    function setFlashLoansPaused(bool _paused) external requiresAuth {
412
        ICdpManagerData(cdpManagerAddress).syncGlobalAccountingAndGracePeriod(); // Accrue State First
413

                            
                        
414
        flashLoansPaused = _paused;
415
        emit FlashLoansPaused(msg.sender, _paused);
416
    }
417
}
418

                            
                        

Lines covered: 232 / 331 (70.1%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
import "./Interfaces/IBorrowerOperations.sol";
6
import "./Interfaces/ICdpManager.sol";
7
import "./Interfaces/ICdpManagerData.sol";
8
import "./Interfaces/IEBTCToken.sol";
9
import "./Interfaces/ICollSurplusPool.sol";
10
import "./Interfaces/ISortedCdps.sol";
11
import "./Dependencies/EbtcBase.sol";
12
import "./Dependencies/ReentrancyGuard.sol";
13
import "./Dependencies/Ownable.sol";
14
import "./Dependencies/AuthNoOwner.sol";
15
import "./Dependencies/ERC3156FlashLender.sol";
16
import "./Dependencies/PermitNonce.sol";
17

                            
                        
18
contract BorrowerOperations is
19
    EbtcBase,
20
    ReentrancyGuard,
21
    IBorrowerOperations,
22
    ERC3156FlashLender,
23
    AuthNoOwner,
24
    PermitNonce
25
{
26
    string public constant NAME = "BorrowerOperations";
27

                            
                        
28
    // keccak256("permitPositionManagerApproval(address borrower,address positionManager,uint8 status,uint256 nonce,uint256 deadline)");
29
    bytes32 private constant _PERMIT_POSITION_MANAGER_TYPEHASH =
30
        keccak256(
31
            "PermitPositionManagerApproval(address borrower,address positionManager,uint8 status,uint256 nonce,uint256 deadline)"
32
        );
33

                            
                        
34
    // keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
35
    bytes32 private constant _TYPE_HASH =
36
        0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f;
37

                            
                        
38
    string internal constant _VERSION = "1";
39

                            
                        
40
    // Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to
41
    // invalidate the cached domain separator if the chain id changes.
42
    bytes32 private immutable _CACHED_DOMAIN_SEPARATOR;
43
    uint256 private immutable _CACHED_CHAIN_ID;
44

                            
                        
45
    bytes32 private immutable _HASHED_NAME;
46
    bytes32 private immutable _HASHED_VERSION;
47

                            
                        
48
    // --- Connected contract declarations ---
49

                            
                        
50
    ICdpManager public immutable cdpManager;
51

                            
                        
52
    ICollSurplusPool public immutable collSurplusPool;
53

                            
                        
54
    address public feeRecipientAddress;
55

                            
                        
56
    IEBTCToken public immutable ebtcToken;
57

                            
                        
58
    // A doubly linked list of Cdps, sorted by their collateral ratios
59
    ISortedCdps public immutable sortedCdps;
60

                            
                        
61
    // Mapping of borrowers to approved position managers, by approval status: cdpOwner(borrower) -> positionManager -> PositionManagerApproval (None, OneTime, Persistent)
62
    mapping(address => mapping(address => PositionManagerApproval)) public positionManagers;
63

                            
                        
64
    /* --- Variable container structs  ---
65

                            
                        
66
    Used to hold, return and assign variables inside a function, in order to avoid the error:
67
    "CompilerError: Stack too deep". */
68

                            
                        
69
    struct LocalVariables_adjustCdp {
70
        uint256 price;
71
        uint256 collChange;
72
        uint256 netDebtChange;
73
        bool isCollIncrease;
74
        uint256 debt;
75
        uint256 coll;
76
        uint256 oldICR;
77
        uint256 newICR;
78
        uint256 newTCR;
79
        uint256 newDebt;
80
        uint256 newColl;
81
        uint256 stake;
82
    }
83

                            
                        
84
    struct LocalVariables_openCdp {
85
        uint256 price;
86
        uint256 debt;
87
        uint256 totalColl;
88
        uint256 netColl;
89
        uint256 ICR;
90
        uint256 NICR;
91
        uint256 stake;
92
        uint256 arrayIndex;
93
    }
94

                            
                        
95
    struct LocalVariables_moveTokens {
96
        address user;
97
        uint256 collChange;
98
        uint256 collAddUnderlying; // ONLY for isCollIncrease=true
99
        bool isCollIncrease;
100
        uint256 EBTCChange;
101
        bool isDebtIncrease;
102
        uint256 netDebtChange;
103
    }
104

                            
                        
105
    // --- Dependency setters ---
106
    constructor(
107
        address _cdpManagerAddress,
108
        address _activePoolAddress,
109
        address _collSurplusPoolAddress,
110
        address _priceFeedAddress,
111
        address _sortedCdpsAddress,
112
        address _ebtcTokenAddress,
113
        address _feeRecipientAddress,
114
        address _collTokenAddress
115
    ) EbtcBase(_activePoolAddress, _priceFeedAddress, _collTokenAddress) {
116
        cdpManager = ICdpManager(_cdpManagerAddress);
117
        collSurplusPool = ICollSurplusPool(_collSurplusPoolAddress);
118
        sortedCdps = ISortedCdps(_sortedCdpsAddress);
119
        ebtcToken = IEBTCToken(_ebtcTokenAddress);
120
        feeRecipientAddress = _feeRecipientAddress;
121

                            
                        
122
        address _authorityAddress = address(AuthNoOwner(_cdpManagerAddress).authority());
123
        if (_authorityAddress != address(0)) {
124
            _initializeAuthority(_authorityAddress);
125
        }
126

                            
                        
127
        bytes32 hashedName = keccak256(bytes(NAME));
128
        bytes32 hashedVersion = keccak256(bytes(_VERSION));
129

                            
                        
130
        _HASHED_NAME = hashedName;
131
        _HASHED_VERSION = hashedVersion;
132
        _CACHED_CHAIN_ID = _chainID();
133
        _CACHED_DOMAIN_SEPARATOR = _buildDomainSeparator(_TYPE_HASH, hashedName, hashedVersion);
134

                            
                        
135
        emit FeeRecipientAddressChanged(_feeRecipientAddress);
136
    }
137

                            
                        
138
    /**
139
        @notice BorrowerOperations and CdpManager share reentrancy status by confirming the other's locked flag before beginning operation
140
        @dev This is an alternative to the more heavyweight solution of both being able to set the reentrancy flag on a 3rd contract.
141
        @dev Prevents multi-contract reentrancy between these two contracts
142
     */
143
    modifier nonReentrantSelfAndCdpM() {
144
        require(locked == OPEN, "BorrowerOperations: Reentrancy in nonReentrant call");
145
        require(
146
            ReentrancyGuard(address(cdpManager)).locked() == OPEN,
147
            "CdpManager: Reentrancy in nonReentrant call"
148
        );
149

                            
                        
150
        locked = LOCKED;
151

                            
                        
152
        _;
153

                            
                        
154
        locked = OPEN;
155
    }
156

                            
                        
157
    // --- Borrower Cdp Operations ---
158

                            
                        
159
    /**
160
    @notice Function that creates a Cdp for the caller with the requested debt, and the stETH received as collateral.
161
    @notice Successful execution is conditional mainly on the resulting collateralization ratio which must exceed the minimum (110% in Normal Mode, 150% in Recovery Mode).
162
    @notice In addition to the requested debt, extra debt is issued to cover the gas compensation.
163
    */
164
    function openCdp(
165
        uint256 _debt,
166
        bytes32 _upperHint,
167
        bytes32 _lowerHint,
168
        uint256 _stEthBalance
169
    ) external override nonReentrantSelfAndCdpM returns (bytes32) {
170
        return _openCdp(_debt, _upperHint, _lowerHint, _stEthBalance, msg.sender);
171
    }
172

                            
                        
173
    function openCdpFor(
174
        uint256 _debt,
175
        bytes32 _upperHint,
176
        bytes32 _lowerHint,
177
        uint256 _collAmount,
178
        address _borrower
179
    ) external override nonReentrantSelfAndCdpM returns (bytes32) {
180
        return _openCdp(_debt, _upperHint, _lowerHint, _collAmount, _borrower);
181
    }
182

                            
                        
183
    // Function that adds the received stETH to the caller's specified Cdp.
184
    function addColl(
185
        bytes32 _cdpId,
186
        bytes32 _upperHint,
187
        bytes32 _lowerHint,
188
        uint256 _stEthBalanceIncrease
189
    ) external override nonReentrantSelfAndCdpM {
190
        _adjustCdpInternal(_cdpId, 0, 0, false, _upperHint, _lowerHint, _stEthBalanceIncrease);
191
    }
192

                            
                        
193
    /**
194
    Withdraws `_stEthBalanceDecrease` amount of collateral from the caller’s Cdp. Executes only if the user has an active Cdp, the withdrawal would not pull the user’s Cdp below the minimum collateralization ratio, and the resulting total collateralization ratio of the system is above 150%.
195
    */
196
    function withdrawColl(
197
        bytes32 _cdpId,
198
        uint256 _stEthBalanceDecrease,
199
        bytes32 _upperHint,
200
        bytes32 _lowerHint
201
    ) external override nonReentrantSelfAndCdpM {
202
        _adjustCdpInternal(_cdpId, _stEthBalanceDecrease, 0, false, _upperHint, _lowerHint, 0);
203
    }
204

                            
                        
205
    // Withdraw EBTC tokens from a cdp: mint new EBTC tokens to the owner, and increase the cdp's debt accordingly
206
    /**
207
    Issues `_amount` of eBTC from the caller’s Cdp to the caller. Executes only if the Cdp's collateralization ratio would remain above the minimum, and the resulting total collateralization ratio is above 150%.
208
     */
209
    function withdrawDebt(
210
        bytes32 _cdpId,
211
        uint256 _debt,
212
        bytes32 _upperHint,
213
        bytes32 _lowerHint
214
    ) external override nonReentrantSelfAndCdpM {
215
        _adjustCdpInternal(_cdpId, 0, _debt, true, _upperHint, _lowerHint, 0);
216
    }
217

                            
                        
218
    // Repay EBTC tokens to a Cdp: Burn the repaid EBTC tokens, and reduce the cdp's debt accordingly
219
    /**
220
    repay `_amount` of eBTC to the caller’s Cdp, subject to leaving 50 debt in the Cdp (which corresponds to the 50 eBTC gas compensation).
221
    */
222
    function repayDebt(
223
        bytes32 _cdpId,
224
        uint256 _debt,
225
        bytes32 _upperHint,
226
        bytes32 _lowerHint
227
    ) external override nonReentrantSelfAndCdpM {
228
        _adjustCdpInternal(_cdpId, 0, _debt, false, _upperHint, _lowerHint, 0);
229
    }
230

                            
                        
231
    function adjustCdp(
232
        bytes32 _cdpId,
233
        uint256 _stEthBalanceDecrease,
234
        uint256 _debtChange,
235
        bool _isDebtIncrease,
236
        bytes32 _upperHint,
237
        bytes32 _lowerHint
238
    ) external override nonReentrantSelfAndCdpM {
239
        _adjustCdpInternal(
240
            _cdpId,
241
            _stEthBalanceDecrease,
242
            _debtChange,
243
            _isDebtIncrease,
244
            _upperHint,
245
            _lowerHint,
246
            0
247
        );
248
    }
249

                            
                        
250
    /**
251
    enables a borrower to simultaneously change both their collateral and debt, subject to all the restrictions that apply to individual increases/decreases of each quantity with the following particularity: if the adjustment reduces the collateralization ratio of the Cdp, the function only executes if the resulting total collateralization ratio is above 150%. The borrower has to provide a `_maxFeePercentage` that he/she is willing to accept in case of a fee slippage, i.e. when a redemption transaction is processed first, driving up the issuance fee. The parameter is ignored if the debt is not increased with the transaction.
252
    */
253
    // TODO optimization candidate
254
    function adjustCdpWithColl(
255
        bytes32 _cdpId,
256
        uint256 _stEthBalanceDecrease,
257
        uint256 _debtChange,
258
        bool _isDebtIncrease,
259
        bytes32 _upperHint,
260
        bytes32 _lowerHint,
261
        uint256 _stEthBalanceIncrease
262
    ) external override nonReentrantSelfAndCdpM {
263
        _adjustCdpInternal(
264
            _cdpId,
265
            _stEthBalanceDecrease,
266
            _debtChange,
267
            _isDebtIncrease,
268
            _upperHint,
269
            _lowerHint,
270
            _stEthBalanceIncrease
271
        );
272
    }
273

                            
                        
274
    /*
275
     * _adjustCdpInternal(): Alongside a debt change, this function can perform either
276
     * a collateral top-up or a collateral withdrawal.
277
     *
278
     * It therefore expects either a positive _stEthBalanceIncrease, or a positive _stEthBalanceDecrease argument.
279
     *
280
     * If both are positive, it will revert.
281
     */
282
    function _adjustCdpInternal(
283
        bytes32 _cdpId,
284
        uint256 _stEthBalanceDecrease,
285
        uint256 _debtChange,
286
        bool _isDebtIncrease,
287
        bytes32 _upperHint,
288
        bytes32 _lowerHint,
289
        uint256 _stEthBalanceIncrease
290
    ) internal {
291
        // Confirm the operation is the borrower or approved position manager adjusting its own cdp
292
        address _borrower = sortedCdps.getOwnerAddress(_cdpId);
293
        _requireBorrowerOrPositionManagerAndUpdate(_borrower);
294

                            
                        
295
        _requireCdpisActive(cdpManager, _cdpId);
296

                            
                        
297
        cdpManager.syncAccounting(_cdpId);
298

                            
                        
299
        LocalVariables_adjustCdp memory vars;
300

                            
                        
301
        vars.price = priceFeed.fetchPrice();
302
        bool isRecoveryMode = _checkRecoveryModeForTCR(_getTCR(vars.price));
303

                            
                        
304
        if (_isDebtIncrease) {
305
            _requireNonZeroDebtChange(_debtChange);
306
        }
307
        _requireSingularCollChange(_stEthBalanceIncrease, _stEthBalanceDecrease);
308
        _requireNonZeroAdjustment(_stEthBalanceIncrease, _stEthBalanceDecrease, _debtChange);
309

                            
                        
310
        // Get the collChange based on the collateral value transferred in the transaction
311
        (vars.collChange, vars.isCollIncrease) = _getCollSharesChangeFromStEthChange(
312
            _stEthBalanceIncrease,
313
            _stEthBalanceDecrease
314
        );
315

                            
                        
316
        vars.netDebtChange = _debtChange;
317

                            
                        
318
        vars.debt = cdpManager.getCdpDebt(_cdpId);
319
        vars.coll = cdpManager.getCdpCollShares(_cdpId);
320

                            
                        
321
        // Get the cdp's old ICR before the adjustment, and what its new ICR will be after the adjustment
322
        uint256 _cdpStEthBalance = collateral.getPooledEthByShares(vars.coll);
323
        require(
324
            _stEthBalanceDecrease <= _cdpStEthBalance,
325
            "BorrowerOperations: withdraw more collateral than CDP has!"
326
        );
327
        vars.oldICR = EbtcMath._computeCR(_cdpStEthBalance, vars.debt, vars.price);
328
        vars.newICR = _getNewICRFromCdpChange(
329
            vars.coll,
330
            vars.debt,
331
            vars.collChange,
332
            vars.isCollIncrease,
333
            vars.netDebtChange,
334
            _isDebtIncrease,
335
            vars.price
336
        );
337

                            
                        
338
        // Check the adjustment satisfies all conditions for the current system mode
339
        _requireValidAdjustmentInCurrentMode(
340
            isRecoveryMode,
341
            _stEthBalanceDecrease,
342
            _isDebtIncrease,
343
            vars
344
        );
345

                            
                        
346
        // When the adjustment is a debt repayment, check it's a valid amount, that the caller has enough EBTC, and that the resulting debt is >0
347
        if (!_isDebtIncrease && _debtChange > 0) {
348
            _requireValidDebtRepayment(vars.debt, vars.netDebtChange);
349
            _requireSufficientEbtcBalance(msg.sender, vars.netDebtChange);
350
            _requireNonZeroDebt(vars.debt - vars.netDebtChange);
351
        }
352

                            
                        
353
        (vars.newColl, vars.newDebt) = _getNewCdpAmounts(
354
            vars.coll,
355
            vars.debt,
356
            vars.collChange,
357
            vars.isCollIncrease,
358
            vars.netDebtChange,
359
            _isDebtIncrease
360
        );
361

                            
                        
362
        _requireAtLeastMinNetStEthBalance(collateral.getPooledEthByShares(vars.newColl));
363

                            
                        
364
        cdpManager.updateCdp(_cdpId, _borrower, vars.coll, vars.debt, vars.newColl, vars.newDebt);
365

                            
                        
366
        // Re-insert cdp in to the sorted list
367
        {
368
            uint256 newNICR = _getNewNominalICRFromCdpChange(vars, _isDebtIncrease);
369
            sortedCdps.reInsert(_cdpId, newNICR, _upperHint, _lowerHint);
370
        }
371

                            
                        
372
        // Use the unmodified _debtChange here, as we don't send the fee to the user
373
        {
374
            LocalVariables_moveTokens memory _varMvTokens = LocalVariables_moveTokens(
375
                msg.sender,
376
                vars.collChange,
377
                (vars.isCollIncrease ? _stEthBalanceIncrease : 0),
378
                vars.isCollIncrease,
379
                _debtChange,
380
                _isDebtIncrease,
381
                vars.netDebtChange
382
            );
383
            _processTokenMovesFromAdjustment(_varMvTokens);
384
        }
385
    }
386

                            
                        
387
    function _openCdp(
388
        uint256 _debt,
389
        bytes32 _upperHint,
390
        bytes32 _lowerHint,
391
        uint256 _stEthBalance,
392
        address _borrower
393
    ) internal returns (bytes32) {
394
        _requireNonZeroDebt(_debt);
395
        _requireBorrowerOrPositionManagerAndUpdate(_borrower);
396

                            
                        
397
        LocalVariables_openCdp memory vars;
398

                            
                        
399
        // ICR is based on the net coll, i.e. the requested coll amount - fixed liquidator incentive gas comp.
400
        vars.netColl = _getNetColl(_stEthBalance);
401

                            
                        
402
        // will revert if _stEthBalance is less than MIN_NET_COLL + LIQUIDATOR_REWARD
403
        _requireAtLeastMinNetStEthBalance(vars.netColl);
404

                            
                        
405
        // Update global pending index before any operations
406
        cdpManager.syncGlobalAccounting();
407

                            
                        
408
        vars.price = priceFeed.fetchPrice();
409
        bool isRecoveryMode = _checkRecoveryModeForTCR(_getTCR(vars.price));
410

                            
                        
411
        vars.debt = _debt;
412

                            
                        
413
        // Sanity check
414
        require(vars.netColl > 0, "BorrowerOperations: zero collateral for openCdp()!");
415

                            
                        
416
        uint256 _netCollAsShares = collateral.getSharesByPooledEth(vars.netColl);
417
        uint256 _liquidatorRewardShares = collateral.getSharesByPooledEth(LIQUIDATOR_REWARD);
418

                            
                        
419
        // ICR is based on the net coll, i.e. the requested coll amount - fixed liquidator incentive gas comp.
420
        vars.ICR = EbtcMath._computeCR(vars.netColl, vars.debt, vars.price);
421

                            
                        
422
        // NICR uses shares to normalize NICR across CDPs opened at different pooled ETH / shares ratios
423
        vars.NICR = EbtcMath._computeNominalCR(_netCollAsShares, vars.debt);
424

                            
                        
425
        /**
426
            In recovery move, ICR must be greater than CCR
427
            CCR > MCR (125% vs 110%)
428

                            
                        
429
            In normal mode, ICR must be greater thatn MCR
430
            Additionally, the new system TCR after the CDPs addition must be >CCR
431
        */
432
        uint256 newTCR = _getNewTCRFromCdpChange(vars.netColl, true, vars.debt, true, vars.price);
433
        if (isRecoveryMode) {
434
            _requireICRisNotBelowCCR(vars.ICR);
435

                            
                        
436
            // == Grace Period == //
437
            // We are in RM, Edge case is Depositing Coll could exit RM
438
            // We check with newTCR
439
            if (newTCR < CCR) {
440
                // Notify RM
441
                cdpManager.notifyStartGracePeriod(newTCR);
442
            } else {
443
                // Notify Back to Normal Mode
444
                cdpManager.notifyEndGracePeriod(newTCR);
445
            }
446
        } else {
447
            _requireICRisNotBelowMCR(vars.ICR);
448
            _requireNewTCRisNotBelowCCR(newTCR);
449

                            
                        
450
            // == Grace Period == //
451
            // We are not in RM, no edge case, we always stay above RM
452
            // Always Notify Back to Normal Mode
453
            cdpManager.notifyEndGracePeriod(newTCR);
454
        }
455

                            
                        
456
        // Set the cdp struct's properties
457
        bytes32 _cdpId = sortedCdps.insert(_borrower, vars.NICR, _upperHint, _lowerHint);
458

                            
                        
459
        // Collision check: collisions should never occur
460
        // Explicitly prevent it by checking for `nonExistent`
461
        _requireCdpIsNonExistent(_cdpId);
462

                            
                        
463
        // Collateral is stored in shares form for normalization
464
        cdpManager.initializeCdp(
465
            _cdpId,
466
            vars.debt,
467
            _netCollAsShares,
468
            _liquidatorRewardShares,
469
            _borrower
470
        );
471

                            
                        
472
        // Mint the full debt amount, in eBTC tokens, to the caller
473
        _withdrawDebt(msg.sender, _debt, _debt);
474

                            
                        
475
        /**
476
            Note that only NET coll (as shares) is considered part of the CDP.
477
            The static liqudiation incentive is stored in the gas pool and can be considered a deposit / voucher to be returned upon CDP close, to the closer.
478
            The close can happen from the borrower closing their own CDP, a full liquidation, or a redemption.
479
        */
480

                            
                        
481
        // CEI: Move the net collateral and liquidator gas compensation to the Active Pool. Track only net collateral shares for TCR purposes.
482
        _activePoolAddColl(_stEthBalance, _netCollAsShares);
483

                            
                        
484
        // Invariant check
485
        require(
486
            vars.netColl + LIQUIDATOR_REWARD == _stEthBalance,
487
            "BorrowerOperations: deposited collateral mismatch!"
488
        );
489

                            
                        
490
        return _cdpId;
491
    }
492

                            
                        
493
    /**
494
    allows a borrower to repay all debt, withdraw all their collateral, and close their Cdp. Requires the borrower have a eBTC balance sufficient to repay their cdp's debt, excluding gas compensation - i.e. `(debt - 50)` eBTC.
495
    */
496
    function closeCdp(bytes32 _cdpId) external override {
497
        address _borrower = sortedCdps.getOwnerAddress(_cdpId);
498
        _requireBorrowerOrPositionManagerAndUpdate(_borrower);
499

                            
                        
500
        _requireCdpisActive(cdpManager, _cdpId);
501

                            
                        
502
        cdpManager.syncAccounting(_cdpId);
503

                            
                        
504
        uint256 price = priceFeed.fetchPrice();
505
        _requireNotInRecoveryMode(_getTCR(price));
506

                            
                        
507
        uint256 coll = cdpManager.getCdpCollShares(_cdpId);
508
        uint256 debt = cdpManager.getCdpDebt(_cdpId);
509
        uint256 liquidatorRewardShares = cdpManager.getCdpLiquidatorRewardShares(_cdpId);
510

                            
                        
511
        _requireSufficientEbtcBalance(msg.sender, debt);
512

                            
                        
513
        uint256 newTCR = _getNewTCRFromCdpChange(
514
            collateral.getPooledEthByShares(coll),
515
            false,
516
            debt,
517
            false,
518
            price
519
        );
520
        _requireNewTCRisNotBelowCCR(newTCR);
521

                            
                        
522
        // == Grace Period == //
523
        // By definition we are not in RM, notify CDPManager to ensure "Glass is on"
524
        cdpManager.notifyEndGracePeriod(newTCR);
525

                            
                        
526
        // We already verified msg.sender is the borrower
527
        cdpManager.closeCdp(_cdpId, msg.sender, debt, coll);
528

                            
                        
529
        // Burn the repaid EBTC from the user's balance
530
        _repayDebt(msg.sender, debt);
531

                            
                        
532
        // CEI: Send the collateral and liquidator reward shares back to the user
533
        activePool.transferSystemCollSharesAndLiquidatorReward(
534
            msg.sender,
535
            coll,
536
            liquidatorRewardShares
537
        );
538
    }
539

                            
                        
540
    /**
541
     * Claim remaining collateral from a redemption or from a liquidation with ICR > MCR in Recovery Mode
542

                            
                        
543
      when a borrower’s Cdp has been fully redeemed from and closed, or liquidated in Recovery Mode with a collateralization ratio above 110%, this function allows the borrower to claim their stETH collateral surplus that remains in the system (collateral - debt upon redemption; collateral - 110% of the debt upon liquidation).
544
     */
545
    function claimSurplusCollShares() external override {
546
        // send ETH from CollSurplus Pool to owner
547
        collSurplusPool.claimSurplusCollShares(msg.sender);
548
    }
549

                            
                        
550
    /// @notice Returns true if the borrower is allowing position manager to act on their behalf
551
    function getPositionManagerApproval(
552
        address _borrower,
553
        address _positionManager
554
    ) external view override returns (PositionManagerApproval) {
555
        return _getPositionManagerApproval(_borrower, _positionManager);
556
    }
557

                            
                        
558
    function _getPositionManagerApproval(
559
        address _borrower,
560
        address _positionManager
561
    ) internal view returns (PositionManagerApproval) {
562
        return positionManagers[_borrower][_positionManager];
563
    }
564

                            
                        
565
    /// @notice Approve an account to take arbitrary actions on your Cdps.
566
    /// @notice Account managers with 'Persistent' status will be able to take actions indefinitely
567
    /// @notice Account managers with 'OneTIme' status will be able to take a single action on one Cdp. Approval will be automatically revoked after one Cdp-related action.
568
    /// @notice Similar to approving tokens, approving a position manager allows _stealing of all positions_ if given to a malicious account.
569
    function setPositionManagerApproval(
570
        address _positionManager,
571
        PositionManagerApproval _approval
572
    ) external override {
573
        _setPositionManagerApproval(msg.sender, _positionManager, _approval);
574
    }
575

                            
                        
576
    function _setPositionManagerApproval(
577
        address _borrower,
578
        address _positionManager,
579
        PositionManagerApproval _approval
580
    ) internal {
581
        positionManagers[_borrower][_positionManager] = _approval;
582
        emit PositionManagerApprovalSet(_borrower, _positionManager, _approval);
583
    }
584

                            
                        
585
    /// @notice Revoke a position manager from taking further actions on your Cdps
586
    /// @notice Similar to approving tokens, approving a position manager allows _stealing of all positions_ if given to a malicious account.
587
    function revokePositionManagerApproval(address _positionManager) external override {
588
        _setPositionManagerApproval(msg.sender, _positionManager, PositionManagerApproval.None);
589
    }
590

                            
                        
591
    /// @notice Allows recipient of delegation to renounce it
592
    function renouncePositionManagerApproval(address _borrower) external override {
593
        _setPositionManagerApproval(_borrower, msg.sender, PositionManagerApproval.None);
594
    }
595

                            
                        
596
    function DOMAIN_SEPARATOR() external view returns (bytes32) {
597
        return domainSeparator();
598
    }
599

                            
                        
600
    function domainSeparator() public view override returns (bytes32) {
601
        if (_chainID() == _CACHED_CHAIN_ID) {
602
            return _CACHED_DOMAIN_SEPARATOR;
603
        } else {
604
            return _buildDomainSeparator(_TYPE_HASH, _HASHED_NAME, _HASHED_VERSION);
605
        }
606
    }
607

                            
                        
608
    function _chainID() private view returns (uint256) {
609
        return block.chainid;
610
    }
611

                            
                        
612
    function _buildDomainSeparator(
613
        bytes32 typeHash,
614
        bytes32 name,
615
        bytes32 version
616
    ) private view returns (bytes32) {
617
        return keccak256(abi.encode(typeHash, name, version, _chainID(), address(this)));
618
    }
619

                            
                        
620
    function version() external pure override returns (string memory) {
621
        return _VERSION;
622
    }
623

                            
                        
624
    function permitTypeHash() external pure override returns (bytes32) {
625
        return _PERMIT_POSITION_MANAGER_TYPEHASH;
626
    }
627

                            
                        
628
    function permitPositionManagerApproval(
629
        address _borrower,
630
        address _positionManager,
631
        PositionManagerApproval _approval,
632
        uint256 _deadline,
633
        uint8 v,
634
        bytes32 r,
635
        bytes32 s
636
    ) external override {
637
        require(_deadline >= block.timestamp, "BorrowerOperations: Position manager permit expired");
638

                            
                        
639
        bytes32 digest = keccak256(
640
            abi.encodePacked(
641
                "\x19\x01",
642
                domainSeparator(),
643
                keccak256(
644
                    abi.encode(
645
                        _PERMIT_POSITION_MANAGER_TYPEHASH,
646
                        _borrower,
647
                        _positionManager,
648
                        _approval,
649
                        _nonces[_borrower]++,
650
                        _deadline
651
                    )
652
                )
653
            )
654
        );
655
        address recoveredAddress = ecrecover(digest, v, r, s);
656
        require(
657
            recoveredAddress != address(0) && recoveredAddress == _borrower,
658
            "BorrowerOperations: Invalid signature"
659
        );
660

                            
                        
661
        _setPositionManagerApproval(_borrower, _positionManager, _approval);
662
    }
663

                            
                        
664
    // --- Helper functions ---
665

                            
                        
666
    function _getCollSharesChangeFromStEthChange(
667
        uint256 _collReceived,
668
        uint256 _requestedCollWithdrawal
669
    ) internal view returns (uint256 collChange, bool isCollIncrease) {
670
        if (_collReceived != 0) {
671
            collChange = collateral.getSharesByPooledEth(_collReceived);
672
            isCollIncrease = true;
673
        } else {
674
            collChange = collateral.getSharesByPooledEth(_requestedCollWithdrawal);
675
        }
676
    }
677

                            
                        
678
    /**
679
        @notice Process the token movements required by a CDP adjustment.
680
        @notice Handles the cases of a debt increase / decrease, and/or a collateral increase / decrease.
681
     */
682
    function _processTokenMovesFromAdjustment(
683
        LocalVariables_moveTokens memory _varMvTokens
684
    ) internal {
685
        // Debt increase: mint change value of new eBTC to user, increment ActivePool eBTC internal accounting
686
        if (_varMvTokens.isDebtIncrease) {
687
            _withdrawDebt(_varMvTokens.user, _varMvTokens.EBTCChange, _varMvTokens.netDebtChange);
688
        } else {
689
            // Debt decrease: burn change value of eBTC from user, decrement ActivePool eBTC internal accounting
690
            _repayDebt(_varMvTokens.user, _varMvTokens.EBTCChange);
691
        }
692

                            
                        
693
        if (_varMvTokens.isCollIncrease) {
694
            // Coll increase: send change value of stETH to Active Pool, increment ActivePool stETH internal accounting
695
            _activePoolAddColl(_varMvTokens.collAddUnderlying, _varMvTokens.collChange);
696
        } else {
697
            // Coll decrease: send change value of stETH to user, decrement ActivePool stETH internal accounting
698
            activePool.transferSystemCollShares(_varMvTokens.user, _varMvTokens.collChange);
699
        }
700
    }
701

                            
                        
702
    /// @notice Send stETH to Active Pool and increase its recorded ETH balance
703
    /// @param _stEthBalance total balance of stETH to send, inclusive of coll and liquidatorRewardShares
704
    /// @param _sharesToTrack coll as shares (exclsuive of liquidator reward shares)
705
    /// @dev Liquidator reward shares are not considered as part of the system for CR purposes.
706
    /// @dev These number of liquidator shares associated with each CDP are stored in the CDP, while the actual tokens float in the active pool
707
    function _activePoolAddColl(uint256 _stEthBalance, uint256 _sharesToTrack) internal {
708
        // NOTE: No need for safe transfer if the collateral asset is standard. Make sure this is the case!
709
        collateral.transferFrom(msg.sender, address(activePool), _stEthBalance);
710
        activePool.increaseSystemCollShares(_sharesToTrack);
711
    }
712

                            
                        
713
    /// @dev Mint specified debt tokens to account and change global debt accounting accordingly
714
    function _withdrawDebt(address _account, uint256 _debt, uint256 _netDebtIncrease) internal {
715
        activePool.increaseSystemDebt(_netDebtIncrease);
716
        ebtcToken.mint(_account, _debt);
717
    }
718

                            
                        
719
    // Burn the specified amount of EBTC from _account and decreases the total active debt
720
    function _repayDebt(address _account, uint256 _debt) internal {
721
        activePool.decreaseSystemDebt(_debt);
722
        ebtcToken.burn(_account, _debt);
723
    }
724

                            
                        
725
    // --- 'Require' wrapper functions ---
726

                            
                        
727
    function _requireSingularCollChange(
728
        uint256 _stEthBalanceIncrease,
729
        uint256 _stEthBalanceDecrease
730
    ) internal pure {
731
        require(
732
            _stEthBalanceIncrease == 0 || _stEthBalanceDecrease == 0,
733
            "BorrowerOperations: Cannot add and withdraw collateral in same operation"
734
        );
735
    }
736

                            
                        
737
    function _requireNonZeroAdjustment(
738
        uint256 _stEthBalanceIncrease,
739
        uint256 _debtChange,
740
        uint256 _stEthBalanceDecrease
741
    ) internal pure {
742
        require(
743
            _stEthBalanceIncrease != 0 || _stEthBalanceDecrease != 0 || _debtChange != 0,
744
            "BorrowerOperations: There must be either a collateral change or a debt change"
745
        );
746
    }
747

                            
                        
748
    function _requireCdpisActive(ICdpManager _cdpManager, bytes32 _cdpId) internal view {
749
        uint256 status = _cdpManager.getCdpStatus(_cdpId);
750
        require(status == 1, "BorrowerOperations: Cdp does not exist or is closed");
751
    }
752

                            
                        
753
    function _requireCdpIsNonExistent(bytes32 _cdpId) internal view {
754
        uint status = cdpManager.getCdpStatus(_cdpId);
755
        require(status == 0, "BorrowerOperations: Cdp is active or has been previously closed");
756
    }
757

                            
                        
758
    function _requireNonZeroDebtChange(uint _debtChange) internal pure {
759
        require(_debtChange > 0, "BorrowerOperations: Debt increase requires non-zero debtChange");
760
    }
761

                            
                        
762
    function _requireNotInRecoveryMode(uint256 _tcr) internal view {
763
        require(
764
            !_checkRecoveryModeForTCR(_tcr),
765
            "BorrowerOperations: Operation not permitted during Recovery Mode"
766
        );
767
    }
768

                            
                        
769
    function _requireNoStEthBalanceDecrease(uint256 _stEthBalanceDecrease) internal pure {
770
        require(
771
            _stEthBalanceDecrease == 0,
772
            "BorrowerOperations: Collateral withdrawal not permitted during Recovery Mode"
773
        );
774
    }
775

                            
                        
776
    function _requireValidAdjustmentInCurrentMode(
777
        bool _isRecoveryMode,
778
        uint256 _stEthBalanceDecrease,
779
        bool _isDebtIncrease,
780
        LocalVariables_adjustCdp memory _vars
781
    ) internal {
782
        /*
783
         *In Recovery Mode, only allow:
784
         *
785
         * - Pure collateral top-up
786
         * - Pure debt repayment
787
         * - Collateral top-up with debt repayment
788
         * - A debt increase combined with a collateral top-up which makes the
789
         * ICR >= 150% and improves the ICR (and by extension improves the TCR).
790
         *
791
         * In Normal Mode, ensure:
792
         *
793
         * - The new ICR is above MCR
794
         * - The adjustment won't pull the TCR below CCR
795
         */
796

                            
                        
797
        _vars.newTCR = _getNewTCRFromCdpChange(
798
            collateral.getPooledEthByShares(_vars.collChange),
799
            _vars.isCollIncrease,
800
            _vars.netDebtChange,
801
            _isDebtIncrease,
802
            _vars.price
803
        );
804

                            
                        
805
        if (_isRecoveryMode) {
806
            _requireNoStEthBalanceDecrease(_stEthBalanceDecrease);
807
            if (_isDebtIncrease) {
808
                _requireICRisNotBelowCCR(_vars.newICR);
809
                _requireNoDecreaseOfICR(_vars.newICR, _vars.oldICR);
810
            }
811

                            
                        
812
            // == Grace Period == //
813
            // We are in RM, Edge case is Depositing Coll could exit RM
814
            // We check with newTCR
815
            if (_vars.newTCR < CCR) {
816
                // Notify RM
817
                cdpManager.notifyStartGracePeriod(_vars.newTCR);
818
            } else {
819
                // Notify Back to Normal Mode
820
                cdpManager.notifyEndGracePeriod(_vars.newTCR);
821
            }
822
        } else {
823
            // if Normal Mode
824
            _requireICRisNotBelowMCR(_vars.newICR);
825
            _requireNewTCRisNotBelowCCR(_vars.newTCR);
826

                            
                        
827
            // == Grace Period == //
828
            // We are not in RM, no edge case, we always stay above RM
829
            // Always Notify Back to Normal Mode
830
            cdpManager.notifyEndGracePeriod(_vars.newTCR);
831
        }
832
    }
833

                            
                        
834
    function _requireICRisNotBelowMCR(uint256 _newICR) internal pure {
835
        require(
836
            _newICR >= MCR,
837
            "BorrowerOperations: An operation that would result in ICR < MCR is not permitted"
838
        );
839
    }
840

                            
                        
841
    function _requireICRisNotBelowCCR(uint256 _newICR) internal pure {
842
        require(_newICR >= CCR, "BorrowerOperations: Operation must leave cdp with ICR >= CCR");
843
    }
844

                            
                        
845
    function _requireNoDecreaseOfICR(uint256 _newICR, uint256 _oldICR) internal pure {
846
        require(
847
            _newICR >= _oldICR,
848
            "BorrowerOperations: Cannot decrease your Cdp's ICR in Recovery Mode"
849
        );
850
    }
851

                            
                        
852
    function _requireNewTCRisNotBelowCCR(uint256 _newTCR) internal pure {
853
        require(
854
            _newTCR >= CCR,
855
            "BorrowerOperations: An operation that would result in TCR < CCR is not permitted"
856
        );
857
    }
858

                            
                        
859
    function _requireNonZeroDebt(uint256 _debt) internal pure {
860
        require(_debt > 0, "BorrowerOperations: Debt must be non-zero");
861
    }
862

                            
                        
863
    function _requireAtLeastMinNetStEthBalance(uint256 _coll) internal pure {
864
        require(
865
            _coll >= MIN_NET_COLL,
866
            "BorrowerOperations: Cdp's net coll must not fall below minimum"
867
        );
868
    }
869

                            
                        
870
    function _requireValidDebtRepayment(uint256 _currentDebt, uint256 _debtRepayment) internal pure {
871
        require(
872
            _debtRepayment <= _currentDebt,
873
            "BorrowerOperations: Amount repaid must not be larger than the Cdp's debt"
874
        );
875
    }
876

                            
                        
877
    function _requireSufficientEbtcBalance(address _account, uint256 _debtRepayment) internal view {
878
        require(
879
            ebtcToken.balanceOf(_account) >= _debtRepayment,
880
            "BorrowerOperations: Caller doesnt have enough eBTC to make repayment"
881
        );
882
    }
883

                            
                        
884
    function _requireBorrowerOrPositionManagerAndUpdate(address _borrower) internal {
885
        if (_borrower == msg.sender) {
886
            return; // Early return, no delegation
887
        }
888

                            
                        
889
        PositionManagerApproval _approval = _getPositionManagerApproval(_borrower, msg.sender);
890
        // Must be an approved position manager at this point
891
        require(
892
            _approval != PositionManagerApproval.None,
893
            "BorrowerOperations: Only borrower account or approved position manager can OpenCdp on borrower's behalf"
894
        );
895

                            
                        
896
        // Conditional Adjustment
897
        /// @dev If this is a position manager operation with a one-time approval, clear that approval
898
        /// @dev If the PositionManagerApproval was none, we should have failed with the check in _requireBorrowerOrPositionManagerAndUpdate
899
        if (_approval == PositionManagerApproval.OneTime) {
900
            _setPositionManagerApproval(_borrower, msg.sender, PositionManagerApproval.None);
901
        }
902
    }
903

                            
                        
904
    // --- ICR and TCR getters ---
905

                            
                        
906
    // Compute the new collateral ratio, considering the change in coll and debt. Assumes 0 pending rewards.
907
    function _getNewNominalICRFromCdpChange(
908
        LocalVariables_adjustCdp memory vars,
909
        bool _isDebtIncrease
910
    ) internal pure returns (uint256) {
911
        (uint256 newColl, uint256 newDebt) = _getNewCdpAmounts(
912
            vars.coll,
913
            vars.debt,
914
            vars.collChange,
915
            vars.isCollIncrease,
916
            vars.netDebtChange,
917
            _isDebtIncrease
918
        );
919

                            
                        
920
        uint256 newNICR = EbtcMath._computeNominalCR(newColl, newDebt);
921
        return newNICR;
922
    }
923

                            
                        
924
    // Compute the new collateral ratio, considering the change in coll and debt. Assumes 0 pending rewards.
925
    function _getNewICRFromCdpChange(
926
        uint256 _coll,
927
        uint256 _debt,
928
        uint256 _collChange,
929
        bool _isCollIncrease,
930
        uint256 _debtChange,
931
        bool _isDebtIncrease,
932
        uint256 _price
933
    ) internal view returns (uint256) {
934
        (uint256 newColl, uint256 newDebt) = _getNewCdpAmounts(
935
            _coll,
936
            _debt,
937
            _collChange,
938
            _isCollIncrease,
939
            _debtChange,
940
            _isDebtIncrease
941
        );
942

                            
                        
943
        uint256 newICR = EbtcMath._computeCR(
944
            collateral.getPooledEthByShares(newColl),
945
            newDebt,
946
            _price
947
        );
948
        return newICR;
949
    }
950

                            
                        
951
    function _getNewCdpAmounts(
952
        uint256 _coll,
953
        uint256 _debt,
954
        uint256 _collChange,
955
        bool _isCollIncrease,
956
        uint256 _debtChange,
957
        bool _isDebtIncrease
958
    ) internal pure returns (uint256, uint256) {
959
        uint256 newColl = _coll;
960
        uint256 newDebt = _debt;
961

                            
                        
962
        newColl = _isCollIncrease ? _coll + _collChange : _coll - _collChange;
963
        newDebt = _isDebtIncrease ? _debt + _debtChange : _debt - _debtChange;
964

                            
                        
965
        return (newColl, newDebt);
966
    }
967

                            
                        
968
    function _getNewTCRFromCdpChange(
969
        uint256 _collChange,
970
        bool _isCollIncrease,
971
        uint256 _debtChange,
972
        bool _isDebtIncrease,
973
        uint256 _price
974
    ) internal view returns (uint256) {
975
        uint256 _shareColl = getSystemCollShares();
976
        uint256 totalColl = collateral.getPooledEthByShares(_shareColl);
977
        uint256 totalDebt = _getSystemDebt();
978

                            
                        
979
        totalColl = _isCollIncrease ? totalColl + _collChange : totalColl - _collChange;
980
        totalDebt = _isDebtIncrease ? totalDebt + _debtChange : totalDebt - _debtChange;
981

                            
                        
982
        uint256 newTCR = EbtcMath._computeCR(totalColl, totalDebt, _price);
983
        return newTCR;
984
    }
985

                            
                        
986
    // === Flash Loans === //
987
    function flashLoan(
988
        IERC3156FlashBorrower receiver,
989
        address token,
990
        uint256 amount,
991
        bytes calldata data
992
    ) external override returns (bool) {
993
        require(amount > 0, "BorrowerOperations: 0 Amount");
994
        uint256 fee = flashFee(token, amount); // NOTE: Check for `eBTCToken` is implicit here // NOTE: Pause check is here
995
        require(amount <= maxFlashLoan(token), "BorrowerOperations: Too much");
996

                            
                        
997
        // Issue EBTC
998
        ebtcToken.mint(address(receiver), amount);
999

                            
                        
1000
        // Callback
1001
        require(
1002
            receiver.onFlashLoan(msg.sender, token, amount, fee, data) == FLASH_SUCCESS_VALUE,
1003
            "IERC3156: Callback failed"
1004
        );
1005

                            
                        
1006
        // Gas: Repay from user balance, so we don't trigger a new SSTORE
1007
        // Safe to use transferFrom and unchecked as it's a standard token
1008
        // Also saves gas
1009
        // Send both fee and amount to FEE_RECIPIENT, to burn allowance per EIP-3156
1010
        ebtcToken.transferFrom(address(receiver), feeRecipientAddress, fee + amount);
1011

                            
                        
1012
        // Burn amount, from FEE_RECIPIENT
1013
        ebtcToken.burn(feeRecipientAddress, amount);
1014

                            
                        
1015
        emit FlashLoanSuccess(address(receiver), token, amount, fee);
1016

                            
                        
1017
        return true;
1018
    }
1019

                            
                        
1020
    function flashFee(address token, uint256 amount) public view override returns (uint256) {
1021
        require(token == address(ebtcToken), "BorrowerOperations: EBTC Only");
1022
        require(!flashLoansPaused, "BorrowerOperations: Flash Loans Paused");
1023

                            
                        
1024
        return (amount * feeBps) / MAX_BPS;
1025
    }
1026

                            
                        
1027
    /// @dev Max flashloan, exclusively in ETH equals to the current balance
1028
    function maxFlashLoan(address token) public view override returns (uint256) {
1029
        if (token != address(ebtcToken)) {
1030
            return 0;
1031
        }
1032

                            
                        
1033
        if (flashLoansPaused) {
1034
            return 0;
1035
        }
1036

                            
                        
1037
        return type(uint112).max;
1038
    }
1039

                            
                        
1040
    // === Governed Functions ==
1041

                            
                        
1042
    function setFeeRecipientAddress(address _feeRecipientAddress) external requiresAuth {
1043
        require(
1044
            _feeRecipientAddress != address(0),
1045
            "BorrowerOperations: Cannot set feeRecipient to zero address"
1046
        );
1047

                            
                        
1048
        cdpManager.syncGlobalAccounting();
1049

                            
                        
1050
        feeRecipientAddress = _feeRecipientAddress;
1051
        emit FeeRecipientAddressChanged(_feeRecipientAddress);
1052
    }
1053

                            
                        
1054
    function setFeeBps(uint256 _newFee) external requiresAuth {
1055
        require(_newFee <= MAX_FEE_BPS, "ERC3156FlashLender: _newFee should <= MAX_FEE_BPS");
1056

                            
                        
1057
        cdpManager.syncGlobalAccounting();
1058

                            
                        
1059
        // set new flash fee
1060
        uint256 _oldFee = feeBps;
1061
        feeBps = uint16(_newFee);
1062
        emit FlashFeeSet(msg.sender, _oldFee, _newFee);
1063
    }
1064

                            
                        
1065
    function setFlashLoansPaused(bool _paused) external requiresAuth {
1066
        cdpManager.syncGlobalAccounting();
1067

                            
                        
1068
        flashLoansPaused = _paused;
1069
        emit FlashLoansPaused(msg.sender, _paused);
1070
    }
1071
}
1072

                            
                        

Lines covered: 52 / 61 (85.2%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
import "./Interfaces/IPriceFeed.sol";
6
import "./Interfaces/ICdpManager.sol";
7

                            
                        
8
/// @notice The contract allows to check real CR of CDPs
9
///   Acknowledgement: https://github.com/Uniswap/v3-periphery/blob/main/contracts/lens/Quoter.sol
10
contract CRLens {
11
    ICdpManager public immutable cdpManager;
12
    IPriceFeed public immutable priceFeed;
13

                            
                        
14
    constructor(address _cdpManager, address _priceFeed) {
15
        cdpManager = ICdpManager(_cdpManager);
16
        priceFeed = IPriceFeed(_priceFeed);
17
    }
18

                            
                        
19
    // == CORE FUNCTIONS == //
20

                            
                        
21
    /// @notice Returns the TCR of the system after the fee split
22
    /// @dev Call this from offChain with `eth_call` to avoid paying for gas
23
    function getRealTCR(bool revertValue) external returns (uint256) {
24
        // Synch State
25
        cdpManager.syncGlobalAccountingAndGracePeriod();
26

                            
                        
27
        // Return latest
28
        uint price = priceFeed.fetchPrice();
29
        uint256 tcr = cdpManager.getTCR(price);
30

                            
                        
31
        if (revertValue) {
32
            assembly {
33
                let ptr := mload(0x40)
34
                mstore(ptr, tcr)
35
                revert(ptr, 32)
36
            }
37
        }
38

                            
                        
39
        return tcr;
40
    }
41

                            
                        
42
    /// @notice Return the ICR of a CDP after the fee split
43
    /// @dev Call this from offChain with `eth_call` to avoid paying for gas
44
    function getRealICR(bytes32 cdpId, bool revertValue) external returns (uint256) {
45
        cdpManager.syncAccounting(cdpId);
46
        uint price = priceFeed.fetchPrice();
47
        uint256 icr = cdpManager.getICR(cdpId, price);
48

                            
                        
49
        if (revertValue) {
50
            assembly {
51
                let ptr := mload(0x40)
52
                mstore(ptr, icr)
53
                revert(ptr, 32)
54
            }
55
        }
56

                            
                        
57
        return icr;
58
    }
59

                            
                        
60
    /// @notice Return the ICR of a CDP after the fee split
61
    /// @dev Call this from offChain with `eth_call` to avoid paying for gas
62
    function getRealNICR(bytes32 cdpId, bool revertValue) external returns (uint256) {
63
        cdpManager.syncAccounting(cdpId);
64
        uint price = priceFeed.fetchPrice();
65
        uint256 icr = cdpManager.getNominalICR(cdpId);
66

                            
                        
67
        if (revertValue) {
68
            assembly {
69
                let ptr := mload(0x40)
70
                mstore(ptr, icr)
71
                revert(ptr, 32)
72
            }
73
        }
74

                            
                        
75
        return icr;
76
    }
77

                            
                        
78
    /// @dev Returns 1 if we're in RM
79
    function getCheckRecoveryMode(bool revertValue) external returns (uint256) {
80
        // Synch State
81
        cdpManager.syncGlobalAccountingAndGracePeriod();
82

                            
                        
83
        // Return latest
84
        uint price = priceFeed.fetchPrice();
85
        uint256 isRm = cdpManager.checkRecoveryMode(price) == true ? 1 : 0;
86

                            
                        
87
        if (revertValue) {
88
            assembly {
89
                let ptr := mload(0x40)
90
                mstore(ptr, isRm)
91
                revert(ptr, 32)
92
            }
93
        }
94

                            
                        
95
        return isRm;
96
    }
97

                            
                        
98
    // == REVERT LOGIC == //
99
    // Thanks to: https://github.com/Uniswap/v3-periphery/blob/main/contracts/lens/Quoter.sol
100
    // NOTE: You should never use these in prod, these are just for testing //
101

                            
                        
102
    function parseRevertReason(bytes memory reason) private pure returns (uint256) {
103
        if (reason.length != 32) {
104
            if (reason.length < 68) revert("Unexpected error");
105
            assembly {
106
                reason := add(reason, 0x04)
107
            }
108
            revert(abi.decode(reason, (string)));
109
        }
110
        return abi.decode(reason, (uint256));
111
    }
112

                            
                        
113
    /// @notice Returns the TCR of the system after the fee split
114
    /// @dev Call this from offChain with `eth_call` to avoid paying for gas
115
    ///     These cost more gas, there should never be a reason for you to use them beside integration with Echidna
116
    function quoteRealTCR() external returns (uint256) {
117
        try this.getRealTCR(true) {} catch (bytes memory reason) {
118
            return parseRevertReason(reason);
119
        }
120
    }
121

                            
                        
122
    /// @notice Returns the ICR of the system after the fee split
123
    /// @dev Call this from offChain with `eth_call` to avoid paying for gas
124
    ///     These cost more gas, there should never be a reason for you to use them beside integration with Echidna
125
    function quoteRealICR(bytes32 cdpId) external returns (uint256) {
126
        try this.getRealICR(cdpId, true) {} catch (bytes memory reason) {
127
            return parseRevertReason(reason);
128
        }
129
    }
130

                            
                        
131
    /// @notice Returns the NICR of the system after the fee split
132
    /// @dev Call this from offChain with `eth_call` to avoid paying for gas
133
    ///     These cost more gas, there should never be a reason for you to use them beside integration with Echidna
134
    function quoteRealNICR(bytes32 cdpId) external returns (uint256) {
135
        try this.getRealNICR(cdpId, true) {} catch (bytes memory reason) {
136
            return parseRevertReason(reason);
137
        }
138
    }
139

                            
                        
140
    /// @notice Returns whether the system is in RM after taking fee split
141
    /// @dev Call this from offChain with `eth_call` to avoid paying for gas
142
    ///     These cost more gas, there should never be a reason for you to use them beside integration with Echidna
143
    function quoteCheckRecoveryMode() external returns (uint256) {
144
        try this.getCheckRecoveryMode(true) {} catch (bytes memory reason) {
145
            return parseRevertReason(reason);
146
        }
147
    }
148

                            
                        
149
    function quoteAnything(function() external anything ) external returns(uint256) {
150
        try anything() {} catch (bytes memory reason) {
151
            return parseRevertReason(reason);
152
        }
153
    }
154
}
155

                            
                        

Lines covered: 265 / 329 (80.5%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
import "./Interfaces/ICdpManager.sol";
6
import "./Interfaces/ICollSurplusPool.sol";
7
import "./Interfaces/IEBTCToken.sol";
8
import "./Interfaces/ISortedCdps.sol";
9
import "./Dependencies/ICollateralTokenOracle.sol";
10
import "./CdpManagerStorage.sol";
11
import "./EBTCDeployer.sol";
12
import "./Dependencies/Proxy.sol";
13

                            
                        
14
contract CdpManager is CdpManagerStorage, ICdpManager, Proxy {
15
    // --- Dependency setter ---
16

                            
                        
17
    /**
18
     * @notice Constructor for CdpManager contract.
19
     * @dev Sets up dependencies and initial staking reward split.
20
     * @param _liquidationLibraryAddress Address of the liquidation library.
21
     * @param _authorityAddress Address of the authority.
22
     * @param _borrowerOperationsAddress Address of BorrowerOperations.
23
     * @param _collSurplusPoolAddress Address of CollSurplusPool.
24
     * @param _ebtcTokenAddress Address of the eBTC token.
25
     * @param _sortedCdpsAddress Address of the SortedCDPs.
26
     * @param _activePoolAddress Address of the ActivePool.
27
     * @param _priceFeedAddress Address of the price feed.
28
     * @param _collTokenAddress Address of the collateral token.
29
     */
30
    constructor(
31
        address _liquidationLibraryAddress,
32
        address _authorityAddress,
33
        address _borrowerOperationsAddress,
34
        address _collSurplusPoolAddress,
35
        address _ebtcTokenAddress,
36
        address _sortedCdpsAddress,
37
        address _activePoolAddress,
38
        address _priceFeedAddress,
39
        address _collTokenAddress
40
    )
41
        CdpManagerStorage(
42
            _liquidationLibraryAddress,
43
            _authorityAddress,
44
            _borrowerOperationsAddress,
45
            _collSurplusPoolAddress,
46
            _ebtcTokenAddress,
47
            _sortedCdpsAddress,
48
            _activePoolAddress,
49
            _priceFeedAddress,
50
            _collTokenAddress
51
        )
52
    {
53
        stakingRewardSplit = STAKING_REWARD_SPLIT;
54
        // Emit initial value for analytics
55
        emit StakingRewardSplitSet(stakingRewardSplit);
56

                            
                        
57
        (uint256 _oldIndex, uint256 _newIndex) = _readStEthIndex();
58
        _syncStEthIndex(_oldIndex, _newIndex);
59
        systemStEthFeePerUnitIndex = DECIMAL_PRECISION;
60
    }
61

                            
                        
62
    // --- Getters ---
63

                            
                        
64
    /**
65
     * @notice Get the count of CDPs in the system
66
     * @return The number of CDPs.
67
     */
68

                            
                        
69
    function getActiveCdpsCount() external view override returns (uint256) {
70
        return CdpIds.length;
71
    }
72

                            
                        
73
    /**
74
     * @notice Get the CdpId at a given index in the CdpIds array.
75
     * @param _index Index of the CdpIds array.
76
     * @return CDP ID.
77
     */
78
    function getIdFromCdpIdsArray(uint256 _index) external view override returns (bytes32) {
79
        return CdpIds[_index];
80
    }
81

                            
                        
82
    // --- Cdp Liquidation functions ---
83
    // -----------------------------------------------------------------
84
    //    CDP ICR     |       Liquidation Behavior (TODO gas compensation?)
85
    //
86
    //  < MCR         |  debt could be fully repaid by liquidator
87
    //                |  and ALL collateral transferred to liquidator
88
    //                |  OR debt could be partially repaid by liquidator and
89
    //                |  liquidator could get collateral of (repaidDebt * max(LICR, min(ICR, MCR)) / price)
90
    //
91
    //  > MCR & < TCR |  only liquidatable in Recovery Mode (TCR < CCR)
92
    //                |  debt could be fully repaid by liquidator
93
    //                |  and up to (repaid debt * MCR) worth of collateral
94
    //                |  transferred to liquidator while the residue of collateral
95
    //                |  will be available in CollSurplusPool for owner to claim
96
    //                |  OR debt could be partially repaid by liquidator and
97
    //                |  liquidator could get collateral of (repaidDebt * max(LICR, min(ICR, MCR)) / price)
98
    // -----------------------------------------------------------------
99

                            
                        
100
    /// @notice Fully liquidate a single CDP by ID. CDP must meet the criteria for liquidation at the time of execution.
101
    /// @notice callable by anyone, attempts to liquidate the CdpId. Executes successfully if Cdp meets the conditions for liquidation (e.g. in Normal Mode, it liquidates if the Cdp's ICR < the system MCR).
102
    /// @dev forwards msg.data directly to the liquidation library using OZ proxy core delegation function
103
    /// @param _cdpId ID of the CDP to liquidate.
104

                            
                        
105
    function liquidate(bytes32 _cdpId) external override {
106
        _delegate(liquidationLibrary);
107
    }
108

                            
                        
109
    /// @notice Partially liquidate a single CDP.
110
    /// @dev forwards msg.data directly to the liquidation library using OZ proxy core delegation function
111
    /// @param _cdpId ID of the CDP to partially liquidate.
112
    /// @param _partialAmount Amount to partially liquidate.
113
    /// @param _upperPartialHint Upper hint for reinsertion of the CDP into the linked list.
114
    /// @param _lowerPartialHint Lower hint for reinsertion of the CDP into the linked list.
115
    function partiallyLiquidate(
116
        bytes32 _cdpId,
117
        uint256 _partialAmount,
118
        bytes32 _upperPartialHint,
119
        bytes32 _lowerPartialHint
120
    ) external override {
121
        _delegate(liquidationLibrary);
122
    }
123

                            
                        
124
    // --- Batch/Sequence liquidation functions ---
125

                            
                        
126
    /// @notice Attempt to liquidate a custom list of CDPs provided by the caller
127
    /// @notice Callable by anyone, accepts a custom list of Cdps addresses as an argument. Steps through the provided list and attempts to liquidate every Cdp, until it reaches the end or it runs out of gas. A Cdp is liquidated only if it meets the conditions for liquidation. For a batch of 10 Cdps, the gas costs per liquidated Cdp are roughly between 75K-83K, for a batch of 50 Cdps between 54K-69K.
128
    /// @dev forwards msg.data directly to the liquidation library using OZ proxy core delegation function
129
    /// @param _cdpArray Array of CDPs to liquidate.
130
    function batchLiquidateCdps(bytes32[] memory _cdpArray) external override {
131
        _delegate(liquidationLibrary);
132
    }
133

                            
                        
134
    // --- Redemption functions ---
135

                            
                        
136
    /// @notice // Redeem as much collateral as possible from given Cdp in exchange for EBTC up to specified maximum
137
    /// @param _redeemColFromCdp Struct containing variables for redeeming collateral.
138
    /// @return singleRedemption Struct containing redemption values.
139
    function _redeemCollateralFromCdp(
140
        SingleRedemptionInputs memory _redeemColFromCdp
141
    ) internal returns (SingleRedemptionValues memory singleRedemption) {
142
        // Determine the remaining amount (lot) to be redeemed,
143
        // capped by the entire debt of the Cdp minus the liquidation reserve
144
        singleRedemption.debtToRedeem = EbtcMath._min(
145
            _redeemColFromCdp.maxEBTCamount,
146
            Cdps[_redeemColFromCdp.cdpId].debt /// @audit Redeem everything
147
        );
148

                            
                        
149
        singleRedemption.collSharesDrawn = collateral.getSharesByPooledEth(
150
            (singleRedemption.debtToRedeem * DECIMAL_PRECISION) / _redeemColFromCdp.price
151
        );
152

                            
                        
153
        // Repurposing this struct here to avoid stack too deep.
154
        CdpDebtAndCollShares memory _oldDebtAndColl = CdpDebtAndCollShares(
155
            Cdps[_redeemColFromCdp.cdpId].debt,
156
            Cdps[_redeemColFromCdp.cdpId].coll,
157
            0
158
        );
159

                            
                        
160
        // Decrease the debt and collateral of the current Cdp according to the EBTC lot and corresponding ETH to send
161
        uint256 newDebt = _oldDebtAndColl.entireDebt - singleRedemption.debtToRedeem;
162
        uint256 newColl = _oldDebtAndColl.entireColl - singleRedemption.collSharesDrawn;
163

                            
                        
164
        if (newDebt == 0) {
165
            // No debt remains, close CDP
166
            // No debt left in the Cdp, therefore the cdp gets closed
167
            {
168
                address _borrower = sortedCdps.getOwnerAddress(_redeemColFromCdp.cdpId);
169
                uint256 _liquidatorRewardShares = Cdps[_redeemColFromCdp.cdpId]
170
                    .liquidatorRewardShares;
171

                            
                        
172
                singleRedemption.collSurplus = newColl; // Collateral surplus processed on full redemption
173
                singleRedemption.liquidatorRewardShares = _liquidatorRewardShares;
174
                singleRedemption.fullRedemption = true;
175

                            
                        
176
                _closeCdpByRedemption(
177
                    _redeemColFromCdp.cdpId,
178
                    0,
179
                    newColl,
180
                    _liquidatorRewardShares,
181
                    _borrower
182
                );
183

                            
                        
184
                emit CdpUpdated(
185
                    _redeemColFromCdp.cdpId,
186
                    _borrower,
187
                    _oldDebtAndColl.entireDebt,
188
                    _oldDebtAndColl.entireColl,
189
                    0,
190
                    0,
191
                    0,
192
                    CdpOperation.redeemCollateral
193
                );
194
            }
195
        } else {
196
            // Debt remains, reinsert CDP
197
            uint256 newNICR = EbtcMath._computeNominalCR(newColl, newDebt);
198

                            
                        
199
            /*
200
             * If the provided hint is out of date, we bail since trying to reinsert without a good hint will almost
201
             * certainly result in running out of gas.
202
             *
203
             * If the resultant net coll of the partial is less than the minimum, we bail.
204
             */
205
            if (
206
                newNICR != _redeemColFromCdp.partialRedemptionHintNICR ||
207
                collateral.getPooledEthByShares(newColl) < MIN_NET_COLL
208
            ) {
209
                singleRedemption.cancelledPartial = true;
210
                return singleRedemption;
211
            }
212

                            
                        
213
            sortedCdps.reInsert(
214
                _redeemColFromCdp.cdpId,
215
                newNICR,
216
                _redeemColFromCdp.upperPartialRedemptionHint,
217
                _redeemColFromCdp.lowerPartialRedemptionHint
218
            );
219

                            
                        
220
            Cdps[_redeemColFromCdp.cdpId].debt = newDebt;
221
            Cdps[_redeemColFromCdp.cdpId].coll = newColl;
222
            _updateStakeAndTotalStakes(_redeemColFromCdp.cdpId);
223

                            
                        
224
            address _borrower = ISortedCdps(sortedCdps).getOwnerAddress(_redeemColFromCdp.cdpId);
225
            emit CdpUpdated(
226
                _redeemColFromCdp.cdpId,
227
                _borrower,
228
                _oldDebtAndColl.entireDebt,
229
                _oldDebtAndColl.entireColl,
230
                newDebt,
231
                newColl,
232
                Cdps[_redeemColFromCdp.cdpId].stake,
233
                CdpOperation.redeemCollateral
234
            );
235
        }
236

                            
                        
237
        return singleRedemption;
238
    }
239

                            
                        
240
    /*
241
     * Called when a full redemption occurs, and closes the cdp.
242
     * The redeemer swaps (debt) EBTC for (debt)
243
     * worth of stETH, so the stETH liquidation reserve is all that remains.
244
     * In order to close the cdp, the stETH liquidation reserve is returned to the CDP owner,
245
     * The debt recorded on the cdp's struct is zero'd elswhere, in _closeCdp.
246
     * Any surplus stETH left in the cdp, is sent to the Coll surplus pool, and can be later claimed by the borrower.
247
     */
248
    function _closeCdpByRedemption(
249
        bytes32 _cdpId, // TODO: Remove?
250
        uint256 _EBTC,
251
        uint256 _collSurplus,
252
        uint256 _liquidatorRewardShares,
253
        address _borrower
254
    ) internal {
255
        _closeCdpWithoutRemovingSortedCdps(_cdpId, Status.closedByRedemption);
256

                            
                        
257
        // Update Active Pool EBTC, and send ETH to account
258
        activePool.decreaseSystemDebt(_EBTC);
259

                            
                        
260
        // Register stETH surplus from upcoming transfers of stETH collateral and liquidator reward shares
261
        collSurplusPool.increaseSurplusCollShares(_borrower, _collSurplus + _liquidatorRewardShares);
262

                            
                        
263
        // CEI: send stETH coll and liquidator reward shares from Active Pool to CollSurplus Pool
264
        activePool.transferSystemCollSharesAndLiquidatorReward(
265
            address(collSurplusPool),
266
            _collSurplus,
267
            _liquidatorRewardShares
268
        );
269
    }
270

                            
                        
271
    /// @notice Returns true if the CdpId specified is the lowest-ICR Cdp in the linked list that still has MCR > ICR
272
    /// @dev Returns false if the specified CdpId hint is blank
273
    /// @dev Returns false if the specified CdpId hint doesn't exist in the list
274
    /// @dev Returns false if the ICR of the specified CdpId is < MCR
275
    /// @dev Returns true if the specified CdpId is not blank, exists in the list, has an ICR > MCR, and the next lower Cdp in the list is either blank or has an ICR < MCR.
276
    function _isValidFirstRedemptionHint(
277
        bytes32 _firstRedemptionHint,
278
        uint256 _price
279
    ) internal view returns (bool) {
280
        if (
281
            _firstRedemptionHint == sortedCdps.nonExistId() ||
282
            !sortedCdps.contains(_firstRedemptionHint) ||
283
            getSyncedICR(_firstRedemptionHint, _price) < MCR
284
        ) {
285
            return false;
286
        }
287

                            
                        
288
        bytes32 nextCdp = sortedCdps.getNext(_firstRedemptionHint);
289
        return nextCdp == sortedCdps.nonExistId() || getSyncedICR(nextCdp, _price) < MCR;
290
    }
291

                            
                        
292
    /** 
293
    redeems `_debt` of eBTC for stETH from the system. Decreases the caller’s eBTC balance, and sends them the corresponding amount of stETH. Executes successfully if the caller has sufficient eBTC to redeem. The number of Cdps redeemed from is capped by `_maxIterations`. The borrower has to provide a `_maxFeePercentage` that he/she is willing to accept in case of a fee slippage, i.e. when another redemption transaction is processed first, driving up the redemption fee.
294
    */
295

                            
                        
296
    /* Send _debt EBTC to the system and redeem the corresponding amount of collateral
297
     * from as many Cdps as are needed to fill the redemption
298
     * request.  Applies pending rewards to a Cdp before reducing its debt and coll.
299
     *
300
     * Note that if _amount is very large, this function can run out of gas, specially if traversed cdps are small.
301
     * This can be easily avoided by
302
     * splitting the total _amount in appropriate chunks and calling the function multiple times.
303
     *
304
     * Param `_maxIterations` can also be provided, so the loop through Cdps is capped
305
     * (if it’s zero, it will be ignored).This makes it easier to
306
     * avoid OOG for the frontend, as only knowing approximately the average cost of an iteration is enough,
307
     * without needing to know the “topology”
308
     * of the cdp list. It also avoids the need to set the cap in stone in the contract,
309
     * nor doing gas calculations, as both gas price and opcode costs can vary.
310
     *
311
     * All Cdps that are redeemed from -- with the likely exception of the last one -- will end up with no debt left,
312
     * therefore they will be closed.
313
     * If the last Cdp does have some remaining debt, it has a finite ICR, and the reinsertion
314
     * could be anywhere in the list, therefore it requires a hint.
315
     * A frontend should use getRedemptionHints() to calculate what the ICR of this Cdp will be after redemption,
316
     * and pass a hint for its position
317
     * in the sortedCdps list along with the ICR value that the hint was found for.
318
     *
319
     * If another transaction modifies the list between calling getRedemptionHints()
320
     * and passing the hints to redeemCollateral(), it is very likely that the last (partially)
321
     * redeemed Cdp would end up with a different ICR than what the hint is for. In this case the
322
     * redemption will stop after the last completely redeemed Cdp and the sender will keep the
323
     * remaining EBTC amount, which they can attempt to redeem later.
324
     */
325
    function redeemCollateral(
326
        uint256 _debt,
327
        bytes32 _firstRedemptionHint,
328
        bytes32 _upperPartialRedemptionHint,
329
        bytes32 _lowerPartialRedemptionHint,
330
        uint256 _partialRedemptionHintNICR,
331
        uint256 _maxIterations,
332
        uint256 _maxFeePercentage
333
    ) external override nonReentrantSelfAndBOps {
334
        RedemptionTotals memory totals;
335

                            
                        
336
        // early check to ensure redemption is not paused
337
        require(redemptionsPaused == false, "CdpManager: Redemptions Paused");
338

                            
                        
339
        _requireValidMaxFeePercentage(_maxFeePercentage);
340

                            
                        
341
        _syncGlobalAccounting(); // Apply state, we will syncGracePeriod at end of function
342

                            
                        
343
        totals.price = priceFeed.fetchPrice();
344
        {
345
            (
346
                uint256 tcrAtStart,
347
                uint256 systemCollSharesAtStart,
348
                uint256 systemDebtAtStart
349
            ) = _getTCRWithSystemDebtAndCollShares(totals.price);
350
            totals.tcrAtStart = tcrAtStart;
351
            totals.systemCollSharesAtStart = systemCollSharesAtStart;
352
            totals.systemDebtAtStart = systemDebtAtStart;
353
        }
354

                            
                        
355
        _requireTCRisNotBelowMCR(totals.price, totals.tcrAtStart);
356
        _requireAmountGreaterThanZero(_debt);
357

                            
                        
358
        _requireEbtcBalanceCoversRedemptionAndWithinSupply(
359
            msg.sender,
360
            _debt,
361
            totals.systemDebtAtStart
362
        );
363

                            
                        
364
        totals.remainingDebtToRedeem = _debt;
365
        address currentBorrower;
366
        bytes32 _cId = _firstRedemptionHint;
367

                            
                        
368
        if (_isValidFirstRedemptionHint(_firstRedemptionHint, totals.price)) {
369
            currentBorrower = sortedCdps.getOwnerAddress(_firstRedemptionHint);
370
        } else {
371
            _cId = sortedCdps.getLast();
372
            currentBorrower = sortedCdps.getOwnerAddress(_cId);
373
            // Find the first cdp with ICR >= MCR
374
            while (currentBorrower != address(0) && getSyncedICR(_cId, totals.price) < MCR) {
375
                _cId = sortedCdps.getPrev(_cId);
376
                currentBorrower = sortedCdps.getOwnerAddress(_cId);
377
            }
378
        }
379

                            
                        
380
        // Loop through the Cdps starting from the one with lowest collateral
381
        // ratio until _amount of EBTC is exchanged for collateral
382
        if (_maxIterations == 0) {
383
            _maxIterations = type(uint256).max;
384
        }
385

                            
                        
386
        bytes32 _firstRedeemed = _cId;
387
        bytes32 _lastRedeemed = _cId;
388
        uint256 _numCdpsFullyRedeemed;
389

                            
                        
390
        /**
391
            Core Redemption Loop
392
        */
393
        while (
394
            currentBorrower != address(0) && totals.remainingDebtToRedeem > 0 && _maxIterations > 0
395
        ) {
396
            // Save the address of the Cdp preceding the current one, before potentially modifying the list
397
            {
398
                _syncAccounting(_cId); /// @audit This happens even if the re-insertion doesn't
399

                            
                        
400
                SingleRedemptionInputs memory _redeemColFromCdp = SingleRedemptionInputs(
401
                    _cId,
402
                    totals.remainingDebtToRedeem,
403
                    totals.price,
404
                    _upperPartialRedemptionHint,
405
                    _lowerPartialRedemptionHint,
406
                    _partialRedemptionHintNICR
407
                );
408
                SingleRedemptionValues memory singleRedemption = _redeemCollateralFromCdp(
409
                    _redeemColFromCdp
410
                );
411
                // Partial redemption was cancelled (out-of-date hint, or new net debt < minimum),
412
                // therefore we could not redeem from the last Cdp
413
                if (singleRedemption.cancelledPartial) break;
414

                            
                        
415
                totals.debtToRedeem = totals.debtToRedeem + singleRedemption.debtToRedeem;
416
                totals.collSharesDrawn = totals.collSharesDrawn + singleRedemption.collSharesDrawn;
417
                totals.remainingDebtToRedeem =
418
                    totals.remainingDebtToRedeem -
419
                    singleRedemption.debtToRedeem;
420
                totals.totalCollSharesSurplus =
421
                    totals.totalCollSharesSurplus +
422
                    singleRedemption.collSurplus;
423

                            
                        
424
                if (singleRedemption.fullRedemption) {
425
                    _lastRedeemed = _cId;
426
                    _numCdpsFullyRedeemed = _numCdpsFullyRedeemed + 1;
427
                }
428

                            
                        
429
                bytes32 _nextId = sortedCdps.getPrev(_cId);
430
                address nextUserToCheck = sortedCdps.getOwnerAddress(_nextId);
431
                currentBorrower = nextUserToCheck;
432
                _cId = _nextId;
433
            }
434
            _maxIterations--;
435
        }
436
        require(totals.collSharesDrawn > 0, "CdpManager: Unable to redeem any amount");
437

                            
                        
438
        // remove from sortedCdps
439
        if (_numCdpsFullyRedeemed == 1) {
440
            sortedCdps.remove(_firstRedeemed);
441
        } else if (_numCdpsFullyRedeemed > 1) {
442
            bytes32[] memory _toRemoveIds = _getCdpIdsToRemove(
443
                _lastRedeemed,
444
                _numCdpsFullyRedeemed,
445
                _firstRedeemed
446
            );
447
            sortedCdps.batchRemove(_toRemoveIds);
448
        }
449

                            
                        
450
        // Decay the baseRate due to time passed, and then increase it according to the size of this redemption.
451
        // Use the saved total EBTC supply value, from before it was reduced by the redemption.
452
        _updateBaseRateFromRedemption(
453
            totals.collSharesDrawn,
454
            totals.price,
455
            totals.systemDebtAtStart
456
        );
457

                            
                        
458
        // Calculate the ETH fee
459
        totals.feeCollShares = _getRedemptionFee(totals.collSharesDrawn);
460

                            
                        
461
        _requireUserAcceptsFee(totals.feeCollShares, totals.collSharesDrawn, _maxFeePercentage);
462

                            
                        
463
        totals.collSharesToRedeemer = totals.collSharesDrawn - totals.feeCollShares;
464

                            
                        
465
        _syncGracePeriodForGivenValues(
466
            totals.systemCollSharesAtStart - totals.collSharesDrawn - totals.totalCollSharesSurplus,
467
            totals.systemDebtAtStart - totals.debtToRedeem,
468
            totals.price
469
        );
470

                            
                        
471
        emit Redemption(
472
            _debt,
473
            totals.debtToRedeem,
474
            totals.collSharesDrawn,
475
            totals.feeCollShares,
476
            msg.sender
477
        );
478

                            
                        
479
        // Burn the total eBTC that is redeemed
480
        ebtcToken.burn(msg.sender, totals.debtToRedeem);
481

                            
                        
482
        // Update Active Pool eBTC debt internal accounting
483
        activePool.decreaseSystemDebt(totals.debtToRedeem);
484

                            
                        
485
        // Allocate the stETH fee to the FeeRecipient
486
        activePool.allocateSystemCollSharesToFeeRecipient(totals.feeCollShares);
487

                            
                        
488
        // CEI: Send the stETH drawn to the redeemer
489
        activePool.transferSystemCollShares(msg.sender, totals.collSharesToRedeemer);
490
    }
491

                            
                        
492
    // --- Helper functions ---
493

                            
                        
494
    function _getCdpIdsToRemove(
495
        bytes32 _start,
496
        uint256 _total,
497
        bytes32 _end
498
    ) internal view returns (bytes32[] memory) {
499
        uint256 _cnt = _total;
500
        bytes32 _id = _start;
501
        bytes32[] memory _toRemoveIds = new bytes32[](_total);
502
        while (_cnt > 0 && _id != bytes32(0)) {
503
            _toRemoveIds[_total - _cnt] = _id;
504
            _cnt = _cnt - 1;
505
            _id = sortedCdps.getNext(_id);
506
        }
507
        require(_toRemoveIds[0] == _start, "CdpManager: batchRemoveSortedCdpIds check start error");
508
        require(
509
            _toRemoveIds[_total - 1] == _end,
510
            "CdpManager: batchRemoveSortedCdpIds check end error"
511
        );
512
        return _toRemoveIds;
513
    }
514

                            
                        
515
    function syncAccounting(bytes32 _cdpId) external override {
516
        // _requireCallerIsBorrowerOperations(); /// @audit Please check this and let us know if opening this creates issues | TODO: See Stermi Partial Liq
517
        return _syncAccounting(_cdpId);
518
    }
519

                            
                        
520
    // get totalStakes after split fee taken removed
521
    function getTotalStakeForFeeTaken(
522
        uint256 _feeTaken
523
    ) public view override returns (uint256, uint256) {
524
        uint256 stake = _computeNewStake(_feeTaken);
525
        uint256 _newTotalStakes = totalStakes - stake;
526
        return (_newTotalStakes, stake);
527
    }
528

                            
                        
529
    function updateStakeAndTotalStakes(bytes32 _cdpId) external override returns (uint256) {
530
        _requireCallerIsBorrowerOperations();
531
        return _updateStakeAndTotalStakes(_cdpId);
532
    }
533

                            
                        
534
    function closeCdp(
535
        bytes32 _cdpId,
536
        address _borrower,
537
        uint256 _debt,
538
        uint256 _coll
539
    ) external override {
540
        _requireCallerIsBorrowerOperations();
541
        emit CdpUpdated(_cdpId, _borrower, _debt, _coll, 0, 0, 0, CdpOperation.closeCdp);
542
        return _closeCdp(_cdpId, Status.closedByOwner);
543
    }
544

                            
                        
545
    // Push the owner's address to the Cdp owners list, and record the corresponding array index on the Cdp struct
546
    function _addCdpIdToArray(bytes32 _cdpId) internal returns (uint128 index) {
547
        /* Max array size is 2**128 - 1, i.e. ~3e30 cdps. No risk of overflow, since cdps have minimum EBTC
548
        debt of liquidation reserve plus MIN_NET_DEBT.
549
        3e30 EBTC dwarfs the value of all wealth in the world ( which is < 1e15 USD). */
550

                            
                        
551
        // Push the Cdpowner to the array
552
        CdpIds.push(_cdpId);
553

                            
                        
554
        // Record the index of the new Cdpowner on their Cdp struct
555
        index = uint128(CdpIds.length - 1);
556
        Cdps[_cdpId].arrayIndex = index;
557

                            
                        
558
        return index;
559
    }
560

                            
                        
561
    // --- Recovery Mode and TCR functions ---
562

                            
                        
563
    /**
564
    Returns the systemic entire debt assigned to Cdps, i.e. the systemDebt value of the Active Pool.
565
     */
566
    function getSystemDebt() public view returns (uint256 entireSystemDebt) {
567
        return _getSystemDebt();
568
    }
569

                            
                        
570
    /**
571
    returns the total collateralization ratio (TCR) of the system.  The TCR is based on the the entire system debt and collateral (including pending rewards). */
572
    function getTCR(uint256 _price) external view override returns (uint256) {
573
        return _getTCR(_price);
574
    }
575

                            
                        
576
    /**
577
    reveals whether or not the system is in Recovery Mode (i.e. whether the Total Collateralization Ratio (TCR) is below the Critical Collateralization Ratio (CCR)).
578
    */
579
    function checkRecoveryMode(uint256 _price) external view override returns (bool) {
580
        return _checkRecoveryMode(_price);
581
    }
582

                            
                        
583
    // Check whether or not the system *would be* in Recovery Mode,
584
    // given an ETH:USD price, and the entire system coll and debt.
585
    function _checkPotentialRecoveryMode(
586
        uint256 _systemCollShares,
587
        uint256 _systemDebt,
588
        uint256 _price
589
    ) internal view returns (bool) {
590
        uint256 TCR = _computeTCRWithGivenSystemValues(_systemCollShares, _systemDebt, _price);
591
        return TCR < CCR;
592
    }
593

                            
                        
594
    // --- Redemption fee functions ---
595

                            
                        
596
    /*
597
     * This function has two impacts on the baseRate state variable:
598
     * 1) decays the baseRate based on time passed since last redemption or EBTC borrowing operation.
599
     * then,
600
     * 2) increases the baseRate based on the amount redeemed, as a proportion of total supply
601
     */
602
    function _updateBaseRateFromRedemption(
603
        uint256 _ETHDrawn,
604
        uint256 _price,
605
        uint256 _totalEBTCSupply
606
    ) internal returns (uint256) {
607
        uint256 decayedBaseRate = _calcDecayedBaseRate();
608

                            
                        
609
        /* Convert the drawn ETH back to EBTC at face value rate (1 EBTC:1 USD), in order to get
610
         * the fraction of total supply that was redeemed at face value. */
611
        uint256 redeemedEBTCFraction = (collateral.getPooledEthByShares(_ETHDrawn) * _price) /
612
            _totalEBTCSupply;
613

                            
                        
614
        uint256 newBaseRate = decayedBaseRate + (redeemedEBTCFraction / beta);
615
        newBaseRate = EbtcMath._min(newBaseRate, DECIMAL_PRECISION); // cap baseRate at a maximum of 100%
616
        require(newBaseRate > 0, "CdpManager: new baseRate is zero!"); // Base rate is always non-zero after redemption
617

                            
                        
618
        // Update the baseRate state variable
619
        baseRate = newBaseRate;
620
        emit BaseRateUpdated(newBaseRate);
621

                            
                        
622
        _updateLastRedemptionTimestamp();
623

                            
                        
624
        return newBaseRate;
625
    }
626

                            
                        
627
    function getRedemptionRate() public view override returns (uint256) {
628
        return _calcRedemptionRate(baseRate);
629
    }
630

                            
                        
631
    function getRedemptionRateWithDecay() public view override returns (uint256) {
632
        return _calcRedemptionRate(_calcDecayedBaseRate());
633
    }
634

                            
                        
635
    function _calcRedemptionRate(uint256 _baseRate) internal view returns (uint256) {
636
        return
637
            EbtcMath._min(
638
                redemptionFeeFloor + _baseRate,
639
                DECIMAL_PRECISION // cap at a maximum of 100%
640
            );
641
    }
642

                            
                        
643
    function _getRedemptionFee(uint256 _ETHDrawn) internal view returns (uint256) {
644
        return _calcRedemptionFee(getRedemptionRate(), _ETHDrawn);
645
    }
646

                            
                        
647
    function getRedemptionFeeWithDecay(uint256 _ETHDrawn) external view override returns (uint256) {
648
        return _calcRedemptionFee(getRedemptionRateWithDecay(), _ETHDrawn);
649
    }
650

                            
                        
651
    function _calcRedemptionFee(
652
        uint256 _redemptionRate,
653
        uint256 _ETHDrawn
654
    ) internal pure returns (uint256) {
655
        uint256 redemptionFee = (_redemptionRate * _ETHDrawn) / DECIMAL_PRECISION;
656
        require(redemptionFee < _ETHDrawn, "CdpManager: Fee would eat up all returned collateral");
657
        return redemptionFee;
658
    }
659

                            
                        
660
    // Updates the baseRate state variable based on time elapsed since the last redemption or EBTC borrowing operation.
661
    function decayBaseRateFromBorrowing() external override {
662
        _requireCallerIsBorrowerOperations();
663

                            
                        
664
        _decayBaseRate();
665
    }
666

                            
                        
667
    function _decayBaseRate() internal {
668
        uint256 decayedBaseRate = _calcDecayedBaseRate();
669
        require(decayedBaseRate <= DECIMAL_PRECISION, "CdpManager: baseRate too large!"); // The baseRate can decay to 0
670

                            
                        
671
        baseRate = decayedBaseRate;
672
        emit BaseRateUpdated(decayedBaseRate);
673

                            
                        
674
        _updateLastRedemptionTimestamp();
675
    }
676

                            
                        
677
    // --- Internal fee functions ---
678

                            
                        
679
    // Update the last fee operation time only if time passed >= decay interval. This prevents base rate griefing.
680
    function _updateLastRedemptionTimestamp() internal {
681
        uint256 timePassed = block.timestamp > lastRedemptionTimestamp
682
            ? block.timestamp - lastRedemptionTimestamp
683
            : 0;
684

                            
                        
685
        if (timePassed >= SECONDS_IN_ONE_MINUTE) {
686
            // Using the effective elapsed time that is consumed so far to update lastRedemptionTimestamp
687
            // instead block.timestamp for consistency with _calcDecayedBaseRate()
688
            lastRedemptionTimestamp += _minutesPassedSinceLastRedemption() * SECONDS_IN_ONE_MINUTE;
689
            emit LastRedemptionTimestampUpdated(block.timestamp);
690
        }
691
    }
692

                            
                        
693
    function _calcDecayedBaseRate() internal view returns (uint256) {
694
        uint256 minutesPassed = _minutesPassedSinceLastRedemption();
695
        uint256 decayFactor = EbtcMath._decPow(minuteDecayFactor, minutesPassed);
696

                            
                        
697
        return (baseRate * decayFactor) / DECIMAL_PRECISION;
698
    }
699

                            
                        
700
    function _minutesPassedSinceLastRedemption() internal view returns (uint256) {
701
        return
702
            block.timestamp > lastRedemptionTimestamp
703
                ? ((block.timestamp - lastRedemptionTimestamp) / SECONDS_IN_ONE_MINUTE)
704
                : 0;
705
    }
706

                            
                        
707
    function getDeploymentStartTime() public view returns (uint256) {
708
        return deploymentStartTime;
709
    }
710

                            
                        
711
    // Check whether or not the system *would be* in Recovery Mode,
712
    // given an ETH:USD price, and the entire system coll and debt.
713
    function checkPotentialRecoveryMode(
714
        uint256 _systemCollShares,
715
        uint256 _systemDebt,
716
        uint256 _price
717
    ) external view returns (bool) {
718
        return _checkPotentialRecoveryMode(_systemCollShares, _systemDebt, _price);
719
    }
720

                            
                        
721
    // --- 'require' wrapper functions ---
722

                            
                        
723
    function _requireEbtcBalanceCoversRedemptionAndWithinSupply(
724
        address _redeemer,
725
        uint256 _amount,
726
        uint256 _totalSupply
727
    ) internal view {
728
        uint256 callerBalance = ebtcToken.balanceOf(_redeemer);
729
        require(
730
            callerBalance >= _amount,
731
            "CdpManager: Requested redemption amount must be <= user's EBTC token balance"
732
        );
733
        require(
734
            callerBalance <= _totalSupply,
735
            "CdpManager: redeemer's EBTC balance exceeds total supply!"
736
        );
737
    }
738

                            
                        
739
    function _requireAmountGreaterThanZero(uint256 _amount) internal pure {
740
        require(_amount > 0, "CdpManager: Amount must be greater than zero");
741
    }
742

                            
                        
743
    function _requireTCRisNotBelowMCR(uint256 _price, uint256 _TCR) internal view {
744
        require(_TCR >= MCR, "CdpManager: Cannot redeem when TCR < MCR");
745
    }
746

                            
                        
747
    function _requireValidMaxFeePercentage(uint256 _maxFeePercentage) internal view {
748
        require(
749
            _maxFeePercentage >= redemptionFeeFloor && _maxFeePercentage <= DECIMAL_PRECISION,
750
            "Max fee percentage must be between redemption fee floor and 100%"
751
        );
752
    }
753

                            
                        
754
    // --- Governance Parameters ---
755

                            
                        
756
    function setStakingRewardSplit(uint256 _stakingRewardSplit) external requiresAuth {
757
        require(
758
            _stakingRewardSplit <= MAX_REWARD_SPLIT,
759
            "CDPManager: new staking reward split exceeds max"
760
        );
761

                            
                        
762
        syncGlobalAccountingAndGracePeriod();
763

                            
                        
764
        stakingRewardSplit = _stakingRewardSplit;
765
        emit StakingRewardSplitSet(_stakingRewardSplit);
766
    }
767

                            
                        
768
    function setRedemptionFeeFloor(uint256 _redemptionFeeFloor) external requiresAuth {
769
        require(
770
            _redemptionFeeFloor >= MIN_REDEMPTION_FEE_FLOOR,
771
            "CDPManager: new redemption fee floor is lower than minimum"
772
        );
773
        require(
774
            _redemptionFeeFloor <= DECIMAL_PRECISION,
775
            "CDPManager: new redemption fee floor is higher than maximum"
776
        );
777

                            
                        
778
        syncGlobalAccountingAndGracePeriod();
779

                            
                        
780
        redemptionFeeFloor = _redemptionFeeFloor;
781
        emit RedemptionFeeFloorSet(_redemptionFeeFloor);
782
    }
783

                            
                        
784
    function setMinuteDecayFactor(uint256 _minuteDecayFactor) external requiresAuth {
785
        require(
786
            _minuteDecayFactor >= MIN_MINUTE_DECAY_FACTOR,
787
            "CDPManager: new minute decay factor out of range"
788
        );
789
        require(
790
            _minuteDecayFactor <= MAX_MINUTE_DECAY_FACTOR,
791
            "CDPManager: new minute decay factor out of range"
792
        );
793

                            
                        
794
        syncGlobalAccountingAndGracePeriod();
795

                            
                        
796
        // decay first according to previous factor
797
        _decayBaseRate();
798

                            
                        
799
        // set new factor after decaying
800
        minuteDecayFactor = _minuteDecayFactor;
801
        emit MinuteDecayFactorSet(_minuteDecayFactor);
802
    }
803

                            
                        
804
    function setBeta(uint256 _beta) external requiresAuth {
805
        syncGlobalAccountingAndGracePeriod();
806

                            
                        
807
        _decayBaseRate();
808

                            
                        
809
        beta = _beta;
810
        emit BetaSet(_beta);
811
    }
812

                            
                        
813
    function setRedemptionsPaused(bool _paused) external requiresAuth {
814
        syncGlobalAccountingAndGracePeriod();
815
        _decayBaseRate();
816

                            
                        
817
        redemptionsPaused = _paused;
818
        emit RedemptionsPaused(_paused);
819
    }
820

                            
                        
821
    // --- Cdp property getters ---
822

                            
                        
823
    /// @notice Get status of a CDP. Named values can be found in ICdpManagerData.Status.
824
    function getCdpStatus(bytes32 _cdpId) external view override returns (uint256) {
825
        return uint256(Cdps[_cdpId].status);
826
    }
827

                            
                        
828
    /// @notice Get stake value of a CDP.
829
    function getCdpStake(bytes32 _cdpId) external view override returns (uint256) {
830
        return Cdps[_cdpId].stake;
831
    }
832

                            
                        
833
    /// @notice Get stored debt value of a CDP, in eBTC units. Does not include pending changes from redistributions
834
    function getCdpDebt(bytes32 _cdpId) external view override returns (uint256) {
835
        return Cdps[_cdpId].debt;
836
    }
837

                            
                        
838
    /// @notice Get stored collateral value of a CDP, in stETH shares. Does not include pending changes from redistributions or unprocessed staking yield.
839
    function getCdpCollShares(bytes32 _cdpId) external view override returns (uint256) {
840
        return Cdps[_cdpId].coll;
841
    }
842

                            
                        
843
    /**
844
        @notice Get shares value of the liquidator gas incentive reward stored for a CDP. 
845
        @notice This value is processed when a CDP closes. 
846
        @dev This value is returned to the borrower when they close their own CDP
847
        @dev This value is given to liquidators upon fully liquidating a CDP
848
        @dev This value is sent to the CollSurplusPool for reclaiming by the borrower when their CDP is redeemed
849
    */
850
    function getCdpLiquidatorRewardShares(bytes32 _cdpId) external view override returns (uint256) {
851
        return Cdps[_cdpId].liquidatorRewardShares;
852
    }
853

                            
                        
854
    // --- Cdp property setters, called by BorrowerOperations ---
855

                            
                        
856
    /**
857
        @notice Initiailze all state for new CDP
858
        @dev Only callable by BorrowerOperations, critical trust assumption 
859
        @dev Requires CDP to be already inserted into linked list correctly
860
        @param _cdpId id of CDP to initialize state for. Inserting the blank CDP into the linked list grants this ID
861
        @param _debt debt units of CDP
862
        @param _coll collateral shares of CDP
863
        @param _liquidatorRewardShares collateral shares for CDP gas stipend
864
        @param _borrower borrower address
865
     */
866
    function initializeCdp(
867
        bytes32 _cdpId,
868
        uint256 _debt,
869
        uint256 _coll,
870
        uint256 _liquidatorRewardShares,
871
        address _borrower
872
    ) external {
873
        _requireCallerIsBorrowerOperations();
874

                            
                        
875
        Cdps[_cdpId].debt = _debt;
876
        Cdps[_cdpId].coll = _coll;
877
        Cdps[_cdpId].status = Status.active;
878
        Cdps[_cdpId].liquidatorRewardShares = _liquidatorRewardShares;
879

                            
                        
880
        _applyAccumulatedFeeSplit(_cdpId);
881
        _updateRedistributedDebtSnapshot(_cdpId);
882
        uint256 stake = _updateStakeAndTotalStakes(_cdpId);
883
        uint256 index = _addCdpIdToArray(_cdpId);
884

                            
                        
885
        // Previous debt and coll are by definition zero upon opening a new CDP
886
        emit CdpUpdated(_cdpId, _borrower, 0, 0, _debt, _coll, stake, CdpOperation.openCdp);
887
    }
888

                            
                        
889
    /**
890
        @notice Set new CDP debt and collateral values, updating stake accordingly.
891
        @dev Only callable by BorrowerOperations, critical trust assumption 
892
        @param _cdpId Id of CDP to update state for
893
        @param _borrower borrower of CDP. Passed along in function to avoid an extra storage read.
894
        @param _coll collateral shares of CDP before update operation. Passed in function to avoid an extra stroage read.
895
        @param _debt debt units of CDP before update operation. Passed in function to avoid an extra stroage read.
896
        @param _newColl collateral shares of CDP after update operation.
897
        @param _newDebt debt units of CDP after update operation.
898
     */
899
    function updateCdp(
900
        bytes32 _cdpId,
901
        address _borrower,
902
        uint256 _coll,
903
        uint256 _debt,
904
        uint256 _newColl,
905
        uint256 _newDebt
906
    ) external {
907
        _requireCallerIsBorrowerOperations();
908

                            
                        
909
        _setCdpCollShares(_cdpId, _newColl);
910
        _setCdpDebt(_cdpId, _newDebt);
911

                            
                        
912
        uint256 stake = _updateStakeAndTotalStakes(_cdpId);
913

                            
                        
914
        emit CdpUpdated(
915
            _cdpId,
916
            _borrower,
917
            _debt,
918
            _coll,
919
            _newDebt,
920
            _newColl,
921
            stake,
922
            CdpOperation.adjustCdp
923
        );
924
    }
925

                            
                        
926
    /**
927
     * @notice Set the collateral of a CDP
928
     * @param _cdpId The ID of the CDP
929
     * @param _newColl New collateral value, in stETH shares
930
     */
931
    function _setCdpCollShares(bytes32 _cdpId, uint256 _newColl) internal {
932
        Cdps[_cdpId].coll = _newColl;
933
    }
934

                            
                        
935
    /**
936
     * @notice Set the debt of a CDP
937
     * @param _cdpId The ID of the CDP
938
     * @param _newDebt New debt units value
939
     */
940
    function _setCdpDebt(bytes32 _cdpId, uint256 _newDebt) internal {
941
        Cdps[_cdpId].debt = _newDebt;
942
    }
943
}
944

                            
                        

Lines covered: 273 / 324 (84.3%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
import "./Interfaces/ICdpManager.sol";
6
import "./Interfaces/ICollSurplusPool.sol";
7
import "./Interfaces/IEBTCToken.sol";
8
import "./Interfaces/ISortedCdps.sol";
9
import "./Dependencies/EbtcBase.sol";
10
import "./Dependencies/ReentrancyGuard.sol";
11
import "./Dependencies/ICollateralTokenOracle.sol";
12
import "./Dependencies/AuthNoOwner.sol";
13

                            
                        
14
/**
15
    @notice CDP Manager storage and shared functions
16
    @dev CDP Manager was split to get around contract size limitations, liquidation related functions are delegated to LiquidationLibrary contract code.
17
    @dev Both must maintain the same storage layout, so shared storage components where placed here
18
    @dev Shared functions were also added here to de-dup code
19
 */
20
contract CdpManagerStorage is EbtcBase, ReentrancyGuard, ICdpManagerData, AuthNoOwner {
21
    // TODO: IMPROVE
22
    // NOTE: No packing cause it's the last var, no need for u64
23
    uint128 public constant UNSET_TIMESTAMP = type(uint128).max;
24
    uint128 public constant MINIMUM_GRACE_PERIOD = 15 minutes;
25

                            
                        
26
    // TODO: IMPROVE THIS!!!
27
    uint128 public lastGracePeriodStartTimestamp = UNSET_TIMESTAMP; // use max to signify
28
    uint128 public recoveryModeGracePeriodDuration = MINIMUM_GRACE_PERIOD;
29

                            
                        
30
    // TODO: Pitfal is fee split // NOTE: Solved by calling `syncGracePeriod` on external operations from BO
31

                            
                        
32
    /// @notice Start the recovery mode grace period, if the system is in RM and the grace period timestamp has not already been set
33
    /// @dev Trusted function to allow BorrowerOperations actions to set RM Grace Period
34
    /// @dev Assumes BorrowerOperations has correctly calculated and passed in the new system TCR
35
    /// @dev To maintain CEI compliance we use this trusted function
36
    function notifyStartGracePeriod(uint256 tcr) external {
37
        _requireCallerIsBorrowerOperations();
38
        _startGracePeriod(tcr);
39
    }
40

                            
                        
41
    /// @notice End the recovery mode grace period, if the system is no longer in RM
42
    /// @dev Trusted function to allow BorrowerOperations actions to set RM Grace Period
43
    /// @dev Assumes BorrowerOperations has correctly calculated and passed in the new system TCR
44
    /// @dev To maintain CEI compliance we use this trusted function
45
    function notifyEndGracePeriod(uint256 tcr) external {
46
        _requireCallerIsBorrowerOperations();
47
        _endGracePeriod(tcr);
48
    }
49

                            
                        
50
    /// @dev Internal notify called by Redemptions and Liquidations
51
    /// @dev Specified TCR is emitted for notification pruposes regardless of whether the Grace Period timestamp is set
52
    function _startGracePeriod(uint256 _tcr) internal {
53
        emit TCRNotified(_tcr);
54

                            
                        
55
        if (lastGracePeriodStartTimestamp == UNSET_TIMESTAMP) {
56
            lastGracePeriodStartTimestamp = uint128(block.timestamp);
57

                            
                        
58
            emit GracePeriodStart();
59
        }
60
    }
61

                            
                        
62
    /// @notice Clear RM Grace Period timestamp if it has been set
63
    /// @notice No input validation, calling function must confirm that the system is not in recovery mode to be valid
64
    /// @dev Specified TCR is emitted for notification pruposes regardless of whether the Grace Period timestamp is set
65
    /// @dev Internal notify called by Redemptions and Liquidations
66
    function _endGracePeriod(uint256 _tcr) internal {
67
        emit TCRNotified(_tcr);
68

                            
                        
69
        if (lastGracePeriodStartTimestamp != UNSET_TIMESTAMP) {
70
            lastGracePeriodStartTimestamp = UNSET_TIMESTAMP;
71

                            
                        
72
            emit GracePeriodEnd();
73
        }
74
    }
75

                            
                        
76
    function _syncGracePeriod() internal {
77
        uint256 price = priceFeed.fetchPrice();
78
        uint256 tcr = _getTCR(price);
79
        bool isRecoveryMode = _checkRecoveryModeForTCR(tcr);
80

                            
                        
81
        if (isRecoveryMode) {
82
            _startGracePeriod(tcr);
83
        } else {
84
            _endGracePeriod(tcr);
85
        }
86
    }
87

                            
                        
88
    /// @dev Set RM grace period based on specified system collShares, system debt, and price
89
    /// @dev Variant for internal use in redemptions and liquidations
90
    function _syncGracePeriodForGivenValues(
91
        uint256 systemCollShares,
92
        uint256 systemDebt,
93
        uint256 price
94
    ) internal {
95
        // Compute TCR with specified values
96
        uint256 newTCR = EbtcMath._computeCR(
97
            collateral.getPooledEthByShares(systemCollShares),
98
            systemDebt,
99
            price
100
        );
101

                            
                        
102
        if (newTCR < CCR) {
103
            // Notify system is in RM
104
            _startGracePeriod(newTCR);
105
        } else {
106
            // Notify system is outside RM
107
            _endGracePeriod(newTCR);
108
        }
109
    }
110

                            
                        
111
    /// @notice Set grace period duratin
112
    /// @notice Permissioned governance function, must set grace period duration above hardcoded minimum
113
    /// @param _gracePeriod new grace period duration, in seconds
114
    function setGracePeriod(uint128 _gracePeriod) external requiresAuth {
115
        require(
116
            _gracePeriod >= MINIMUM_GRACE_PERIOD,
117
            "CdpManager: Grace period below minimum duration"
118
        );
119

                            
                        
120
        syncGlobalAccountingAndGracePeriod();
121
        recoveryModeGracePeriodDuration = _gracePeriod;
122
        emit GracePeriodDurationSet(_gracePeriod);
123
    }
124

                            
                        
125
    string public constant NAME = "CdpManager";
126

                            
                        
127
    // --- Connected contract declarations ---
128

                            
                        
129
    address public immutable borrowerOperationsAddress;
130

                            
                        
131
    ICollSurplusPool immutable collSurplusPool;
132

                            
                        
133
    IEBTCToken public immutable override ebtcToken;
134

                            
                        
135
    address public immutable liquidationLibrary;
136

                            
                        
137
    // A doubly linked list of Cdps, sorted by their sorted by their collateral ratios
138
    ISortedCdps public immutable sortedCdps;
139

                            
                        
140
    // --- Data structures ---
141

                            
                        
142
    uint256 public constant SECONDS_IN_ONE_MINUTE = 60;
143

                            
                        
144
    uint256 public constant MIN_REDEMPTION_FEE_FLOOR = (DECIMAL_PRECISION * 5) / 1000; // 0.5%
145
    uint256 public redemptionFeeFloor = MIN_REDEMPTION_FEE_FLOOR;
146
    bool public redemptionsPaused;
147
    /*
148
     * Half-life of 12h. 12h = 720 min
149
     * (1/2) = d^720 => d = (1/2)^(1/720)
150
     */
151
    uint256 public minuteDecayFactor = 999037758833783000;
152
    uint256 public constant MIN_MINUTE_DECAY_FACTOR = 1; // Non-zero
153
    uint256 public constant MAX_MINUTE_DECAY_FACTOR = 999999999999999999; // Corresponds to a very fast decay rate, but not too extreme
154

                            
                        
155
    uint256 internal immutable deploymentStartTime;
156

                            
                        
157
    /*
158
     * BETA: 18 digit decimal. Parameter by which to divide the redeemed fraction,
159
     * in order to calc the new base rate from a redemption.
160
     * Corresponds to (1 / ALPHA) in the white paper.
161
     */
162
    uint256 public beta = 2;
163

                            
                        
164
    uint256 public baseRate;
165

                            
                        
166
    uint256 public stakingRewardSplit;
167

                            
                        
168
    // The timestamp of the latest fee operation (redemption or new EBTC issuance)
169
    uint256 public lastRedemptionTimestamp;
170

                            
                        
171
    mapping(bytes32 => Cdp) public Cdps;
172

                            
                        
173
    uint256 public override totalStakes;
174

                            
                        
175
    // Snapshot of the value of totalStakes, taken immediately after the latest liquidation and split fee claim
176
    uint256 public totalStakesSnapshot;
177

                            
                        
178
    // Snapshot of the total collateral across the ActivePool, immediately after the latest liquidation and split fee claim
179
    uint256 public totalCollateralSnapshot;
180

                            
                        
181
    /*
182
     * systemDebtRedistributionIndex track the sums of accumulated liquidation rewards per unit staked.
183
     * During its lifetime, each stake earns:
184
     *
185
     * A systemDebt increase  of ( stake * [systemDebtRedistributionIndex - systemDebtRedistributionIndex(0)] )
186
     *
187
     * Where systemDebtRedistributionIndex(0) are snapshots of systemDebtRedistributionIndex
188
     * for the active Cdp taken at the instant the stake was made
189
     */
190
    uint256 public systemDebtRedistributionIndex;
191

                            
                        
192
    /* Global Index for (Full Price Per Share) of underlying collateral token */
193
    uint256 public override stEthIndex;
194
    /* Global Fee accumulator (never decreasing) per stake unit in CDPManager, similar to systemDebtRedistributionIndex */
195
    uint256 public override systemStEthFeePerUnitIndex;
196
    /* Global Fee accumulator calculation error due to integer division, similar to redistribution calculation */
197
    uint256 public override systemStEthFeePerUnitIndexError;
198
    /* Individual CDP Fee accumulator tracker, used to calculate fee split distribution */
199
    mapping(bytes32 => uint256) public cdpStEthFeePerUnitIndex;
200
    /* Update timestamp for global index */
201
    uint256 lastIndexTimestamp;
202
    // Map active cdps to their RewardSnapshot (eBTC debt redistributed)
203
    mapping(bytes32 => uint256) public cdpDebtRedistributionIndex;
204

                            
                        
205
    // Array of all active cdp Ids - used to to compute an approximate hint off-chain, for the sorted list insertion
206
    bytes32[] public CdpIds;
207

                            
                        
208
    // Error trackers for the cdp redistribution calculation
209
    uint256 public lastEBTCDebtErrorRedistribution;
210

                            
                        
211
    constructor(
212
        address _liquidationLibraryAddress,
213
        address _authorityAddress,
214
        address _borrowerOperationsAddress,
215
        address _collSurplusPool,
216
        address _ebtcToken,
217
        address _sortedCdps,
218
        address _activePool,
219
        address _priceFeed,
220
        address _collateral
221
    ) EbtcBase(_activePool, _priceFeed, _collateral) {
222
        // TODO: Move to setAddresses or _tickInterest?
223
        deploymentStartTime = block.timestamp;
224
        liquidationLibrary = _liquidationLibraryAddress;
225

                            
                        
226
        _initializeAuthority(_authorityAddress);
227

                            
                        
228
        borrowerOperationsAddress = _borrowerOperationsAddress;
229
        collSurplusPool = ICollSurplusPool(_collSurplusPool);
230
        ebtcToken = IEBTCToken(_ebtcToken);
231
        sortedCdps = ISortedCdps(_sortedCdps);
232
    }
233

                            
                        
234
    /**
235
        @notice BorrowerOperations and CdpManager share reentrancy status by confirming the other's locked flag before beginning operation
236
        @dev This is an alternative to the more heavyweight solution of both being able to set the reentrancy flag on a 3rd contract.
237
     */
238
    modifier nonReentrantSelfAndBOps() {
239
        require(locked == OPEN, "CdpManager: Reentrancy in nonReentrant call");
240
        require(
241
            ReentrancyGuard(borrowerOperationsAddress).locked() == OPEN,
242
            "BorrowerOperations: Reentrancy in nonReentrant call"
243
        );
244

                            
                        
245
        locked = LOCKED;
246

                            
                        
247
        _;
248

                            
                        
249
        locked = OPEN;
250
    }
251

                            
                        
252
    function _closeCdp(bytes32 _cdpId, Status closedStatus) internal {
253
        _closeCdpWithoutRemovingSortedCdps(_cdpId, closedStatus);
254
        sortedCdps.remove(_cdpId);
255
    }
256

                            
                        
257
    function _closeCdpWithoutRemovingSortedCdps(bytes32 _cdpId, Status closedStatus) internal {
258
        require(
259
            closedStatus != Status.nonExistent && closedStatus != Status.active,
260
            "CdpManagerStorage: close non-exist or non-active CDP!"
261
        );
262

                            
                        
263
        uint256 cdpIdsArrayLength = CdpIds.length;
264
        _requireMoreThanOneCdpInSystem(cdpIdsArrayLength);
265

                            
                        
266
        _removeStake(_cdpId);
267

                            
                        
268
        Cdps[_cdpId].status = closedStatus;
269
        Cdps[_cdpId].coll = 0;
270
        Cdps[_cdpId].debt = 0;
271
        Cdps[_cdpId].liquidatorRewardShares = 0;
272

                            
                        
273
        cdpDebtRedistributionIndex[_cdpId] = 0;
274
        cdpStEthFeePerUnitIndex[_cdpId] = 0;
275

                            
                        
276
        _removeCdp(_cdpId, cdpIdsArrayLength);
277
    }
278

                            
                        
279
    /*
280
     * Updates snapshots of system total stakes and total collateral,
281
     * excluding a given collateral remainder from the calculation.
282
     * Used in a liquidation sequence.
283
     *
284
     * The calculation excludes a portion of collateral that is in the ActivePool:
285
     *
286
     * the total ETH gas compensation from the liquidation sequence
287
     *
288
     * The ETH as compensation must be excluded as it is always sent out at the very end of the liquidation sequence.
289
     */
290
    function _updateSystemSnapshotsExcludeCollRemainder(uint256 _collRemainder) internal {
291
        uint256 _totalStakesSnapshot = totalStakes;
292
        totalStakesSnapshot = _totalStakesSnapshot;
293

                            
                        
294
        uint256 _totalCollateralSnapshot = activePool.getSystemCollShares() - _collRemainder;
295
        totalCollateralSnapshot = _totalCollateralSnapshot;
296

                            
                        
297
        emit SystemSnapshotsUpdated(_totalStakesSnapshot, _totalCollateralSnapshot);
298
    }
299

                            
                        
300
    /**
301
    get the pending Cdp debt "reward" (i.e. the amount of extra debt assigned to the Cdp) from liquidation redistribution events, earned by their stake
302
    */
303
    function _getPendingRedistributedDebt(
304
        bytes32 _cdpId
305
    ) internal view returns (uint256 pendingEBTCDebtReward) {
306
        Cdp storage cdp = Cdps[_cdpId];
307

                            
                        
308
        if (cdp.status != Status.active) {
309
            return 0;
310
        }
311

                            
                        
312
        uint256 rewardPerUnitStaked = systemDebtRedistributionIndex -
313
            cdpDebtRedistributionIndex[_cdpId];
314

                            
                        
315
        if (rewardPerUnitStaked > 0) {
316
            pendingEBTCDebtReward = (cdp.stake * rewardPerUnitStaked) / DECIMAL_PRECISION;
317
        } else {
318
            return 0;
319
        }
320
    }
321

                            
                        
322
    function _hasRedistributedDebt(bytes32 _cdpId) internal view returns (bool) {
323
        /*
324
         * A Cdp has pending rewards if its snapshot is less than the current rewards per-unit-staked sum:
325
         * this indicates that rewards have occured since the snapshot was made, and the user therefore has
326
         * pending rewards
327
         */
328
        if (Cdps[_cdpId].status != Status.active) {
329
            return false;
330
        }
331

                            
                        
332
        // Returns true if there have been any redemptions
333
        return (cdpDebtRedistributionIndex[_cdpId] < systemDebtRedistributionIndex);
334
    }
335

                            
                        
336
    function _updateRedistributedDebtSnapshot(bytes32 _cdpId) internal {
337
        uint256 _L_EBTCDebt = systemDebtRedistributionIndex;
338

                            
                        
339
        cdpDebtRedistributionIndex[_cdpId] = _L_EBTCDebt;
340
        emit CdpDebtRedistributionIndexUpdated(_cdpId, _L_EBTCDebt);
341
    }
342

                            
                        
343
    // Add the borrowers's coll and debt rewards earned from redistributions, to their Cdp
344
    function _syncAccounting(bytes32 _cdpId) internal {
345
        (, uint _newDebt, , uint _pendingDebt) = _applyAccumulatedFeeSplit(_cdpId);
346

                            
                        
347
        // Update pending debts
348
        if (_pendingDebt > 0) {
349
            Cdp storage _cdp = Cdps[_cdpId];
350
            uint256 prevDebt = _cdp.debt;
351
            uint256 prevColl = _cdp.coll;
352

                            
                        
353
            // Apply pending rewards to cdp's state
354
            _cdp.debt = _newDebt;
355

                            
                        
356
            _updateRedistributedDebtSnapshot(_cdpId);
357

                            
                        
358
            emit CdpUpdated(
359
                _cdpId,
360
                ISortedCdps(sortedCdps).getOwnerAddress(_cdpId),
361
                prevDebt,
362
                prevColl,
363
                _newDebt,
364
                prevColl,
365
                Cdps[_cdpId].stake,
366
                CdpOperation.syncAccounting
367
            );
368
        }
369
    }
370

                            
                        
371
    // Remove borrower's stake from the totalStakes sum, and set their stake to 0
372
    function _removeStake(bytes32 _cdpId) internal {
373
        uint256 _newTotalStakes = totalStakes - Cdps[_cdpId].stake;
374
        totalStakes = _newTotalStakes;
375
        Cdps[_cdpId].stake = 0;
376
        emit TotalStakesUpdated(_newTotalStakes);
377
    }
378

                            
                        
379
    // Update borrower's stake based on their latest collateral value
380
    // and update totalStakes accordingly as well
381
    function _updateStakeAndTotalStakes(bytes32 _cdpId) internal returns (uint256) {
382
        (uint256 newStake, uint256 oldStake) = _updateStakeForCdp(_cdpId);
383

                            
                        
384
        uint256 _newTotalStakes = totalStakes + newStake - oldStake;
385
        totalStakes = _newTotalStakes;
386

                            
                        
387
        emit TotalStakesUpdated(_newTotalStakes);
388

                            
                        
389
        return newStake;
390
    }
391

                            
                        
392
    // Update borrower's stake based on their latest collateral value
393
    function _updateStakeForCdp(bytes32 _cdpId) internal returns (uint256, uint256) {
394
        Cdp storage _cdp = Cdps[_cdpId];
395
        uint256 newStake = _computeNewStake(_cdp.coll);
396
        uint256 oldStake = _cdp.stake;
397
        _cdp.stake = newStake;
398

                            
                        
399
        return (newStake, oldStake);
400
    }
401

                            
                        
402
    // Calculate a new stake based on the snapshots of the totalStakes and totalCollateral taken at the last liquidation
403
    function _computeNewStake(uint256 _coll) internal view returns (uint256) {
404
        uint256 stake;
405
        if (totalCollateralSnapshot == 0) {
406
            stake = _coll;
407
        } else {
408
            /*
409
             * The following check holds true because:
410
             * - The system always contains >= 1 cdp
411
             * - When we close or liquidate a cdp, we redistribute the pending rewards,
412
             * so if all cdps were closed/liquidated,
413
             * rewards would’ve been emptied and totalCollateralSnapshot would be zero too.
414
             */
415
            require(totalStakesSnapshot > 0, "CdpManagerStorage: zero totalStakesSnapshot!");
416
            stake = (_coll * totalStakesSnapshot) / totalCollateralSnapshot;
417
        }
418
        return stake;
419
    }
420

                            
                        
421
    /*
422
     * Remove a Cdp owner from the CdpOwners array, not preserving array order. Removing owner 'B' does the following:
423
     * [A B C D E] => [A E C D], and updates E's Cdp struct to point to its new array index.
424
     */
425
    function _removeCdp(bytes32 _cdpId, uint256 cdpIdsArrayLength) internal {
426
        Status cdpStatus = Cdps[_cdpId].status;
427
        // It’s set in caller function `_closeCdp`
428
        require(
429
            cdpStatus != Status.nonExistent && cdpStatus != Status.active,
430
            "CdpManagerStorage: remove non-exist or non-active CDP!"
431
        );
432

                            
                        
433
        uint128 index = Cdps[_cdpId].arrayIndex;
434
        uint256 length = cdpIdsArrayLength;
435
        uint256 idxLast = length - 1;
436

                            
                        
437
        require(index <= idxLast, "CdpManagerStorage: CDP indexing overflow!");
438

                            
                        
439
        bytes32 idToMove = CdpIds[idxLast];
440

                            
                        
441
        CdpIds[index] = idToMove;
442
        Cdps[idToMove].arrayIndex = index;
443
        emit CdpArrayIndexUpdated(idToMove, index);
444

                            
                        
445
        CdpIds.pop();
446
    }
447

                            
                        
448
    // --- Recovery Mode and TCR functions ---
449

                            
                        
450
    // Calculate TCR given an price, and the entire system coll and debt.
451
    function _computeTCRWithGivenSystemValues(
452
        uint256 _systemCollShares,
453
        uint256 _systemDebt,
454
        uint256 _price
455
    ) internal view returns (uint256) {
456
        uint256 _totalColl = collateral.getPooledEthByShares(_systemCollShares);
457
        return EbtcMath._computeCR(_totalColl, _systemDebt, _price);
458
    }
459

                            
                        
460
    // --- Staking-Reward Fee split functions ---
461

                            
                        
462
    // Claim split fee if there is staking-reward coming
463
    // and update global index & fee-per-unit variables
464
    /// @dev BO can call this without trigggering a
465
    function syncGlobalAccounting() external {
466
        _requireCallerIsBorrowerOperations();
467
        _syncGlobalAccounting();
468
    }
469

                            
                        
470
    function _syncGlobalAccounting() internal {
471
        (uint256 _oldIndex, uint256 _newIndex) = _readStEthIndex();
472
        _syncStEthIndex(_oldIndex, _newIndex);
473
        if (_newIndex > _oldIndex && totalStakes > 0) {
474
            (
475
                uint256 _feeTaken,
476
                uint256 _newFeePerUnit,
477
                uint256 _perUnitError
478
            ) = _calcSyncedGlobalAccounting(_newIndex, _oldIndex);
479
            _takeSplitAndUpdateFeePerUnit(_feeTaken, _newFeePerUnit, _perUnitError);
480
            _updateSystemSnapshotsExcludeCollRemainder(0);
481
        }
482
    }
483

                            
                        
484
    /// @notice Claim Fee Split, toggles Grace Period accordingly
485
    /// @notice Call this if you want to accrue feeSplit
486
    function syncGlobalAccountingAndGracePeriod() public {
487
        _syncGlobalAccounting(); // Apply // Could trigger RM
488
        _syncGracePeriod(); // Synch Grace Period
489
    }
490

                            
                        
491
    /// @return existing(old) local stETH index AND
492
    /// @return current(new) stETH index from collateral token
493
    function _readStEthIndex() internal view returns (uint256, uint256) {
494
        return (stEthIndex, collateral.getPooledEthByShares(DECIMAL_PRECISION));
495
    }
496

                            
                        
497
    // Update the global index via collateral token
498
    function _syncStEthIndex(uint256 _oldIndex, uint256 _newIndex) internal {
499
        if (_newIndex != _oldIndex) {
500
            stEthIndex = _newIndex;
501
            lastIndexTimestamp = block.timestamp;
502
            emit StEthIndexUpdated(_oldIndex, _newIndex, block.timestamp);
503
        }
504
    }
505

                            
                        
506
    // Calculate fee for given pair of collateral indexes, following are returned values:
507
    // - fee split in collateral token which will be deduced from current total system collateral
508
    // - fee split increase per unit, used to update systemStEthFeePerUnitIndex
509
    // - fee split calculation error, used to update systemStEthFeePerUnitIndexError
510
    function calcFeeUponStakingReward(
511
        uint256 _newIndex,
512
        uint256 _prevIndex
513
    ) public view returns (uint256, uint256, uint256) {
514
        require(_newIndex > _prevIndex, "CDPManager: only take fee with bigger new index");
515
        uint256 deltaIndex = _newIndex - _prevIndex;
516
        uint256 deltaIndexFees = (deltaIndex * stakingRewardSplit) / MAX_REWARD_SPLIT;
517

                            
                        
518
        // we take the fee for all CDPs immediately which is scaled by index precision
519
        uint256 _deltaFeeSplit = deltaIndexFees * getSystemCollShares();
520
        uint256 _cachedAllStakes = totalStakes;
521
        // return the values to update the global fee accumulator
522
        uint256 _feeTaken = collateral.getSharesByPooledEth(_deltaFeeSplit) / DECIMAL_PRECISION;
523
        uint256 _deltaFeeSplitShare = (_feeTaken * DECIMAL_PRECISION) +
524
            systemStEthFeePerUnitIndexError;
525
        uint256 _deltaFeePerUnit = _deltaFeeSplitShare / _cachedAllStakes;
526
        uint256 _perUnitError = _deltaFeeSplitShare - (_deltaFeePerUnit * _cachedAllStakes);
527
        return (_feeTaken, _deltaFeePerUnit, _perUnitError);
528
    }
529

                            
                        
530
    // Take the cut from staking reward
531
    // and update global fee-per-unit accumulator
532
    function _takeSplitAndUpdateFeePerUnit(
533
        uint256 _feeTaken,
534
        uint256 _newPerUnit,
535
        uint256 _newErrorPerUnit
536
    ) internal {
537
        uint256 _oldPerUnit = systemStEthFeePerUnitIndex;
538

                            
                        
539
        systemStEthFeePerUnitIndex = _newPerUnit;
540
        systemStEthFeePerUnitIndexError = _newErrorPerUnit;
541

                            
                        
542
        require(activePool.getSystemCollShares() > _feeTaken, "CDPManager: fee split is too big");
543
        activePool.allocateSystemCollSharesToFeeRecipient(_feeTaken);
544

                            
                        
545
        emit CollateralFeePerUnitUpdated(_oldPerUnit, _newPerUnit, _feeTaken);
546
    }
547

                            
                        
548
    // Apply accumulated fee split distributed to the CDP
549
    // and update its accumulator tracker accordingly
550
    function _applyAccumulatedFeeSplit(
551
        bytes32 _cdpId
552
    ) internal returns (uint256, uint256, uint256, uint256) {
553
        // TODO Ensure global states like systemStEthFeePerUnitIndex get timely updated
554
        // whenever there is a CDP modification operation,
555
        // such as opening, closing, adding collateral, repaying debt, or liquidating
556
        // OR Should we utilize some bot-keeper to work the routine job at fixed interval?
557
        _syncGlobalAccounting();
558

                            
                        
559
        uint256 _oldPerUnitCdp = cdpStEthFeePerUnitIndex[_cdpId];
560
        uint256 _systemStEthFeePerUnitIndex = systemStEthFeePerUnitIndex;
561

                            
                        
562
        (
563
            uint256 _newColl,
564
            uint256 _newDebt,
565
            uint256 _feeSplitDistributed,
566
            uint _pendingDebt
567
        ) = _calcSyncedAccounting(_cdpId, _oldPerUnitCdp, _systemStEthFeePerUnitIndex);
568

                            
                        
569
        // apply split fee to given CDP
570
        if (_feeSplitDistributed > 0) {
571
            Cdps[_cdpId].coll = _newColl;
572

                            
                        
573
            emit CdpFeeSplitApplied(
574
                _cdpId,
575
                _oldPerUnitCdp,
576
                _systemStEthFeePerUnitIndex,
577
                _feeSplitDistributed,
578
                _newColl
579
            );
580
        }
581

                            
                        
582
        // sync per stake index for given CDP
583
        if (_oldPerUnitCdp != _systemStEthFeePerUnitIndex) {
584
            cdpStEthFeePerUnitIndex[_cdpId] = _systemStEthFeePerUnitIndex;
585
        }
586

                            
                        
587
        return (_newColl, _newDebt, _feeSplitDistributed, _pendingDebt);
588
    }
589

                            
                        
590
    // return the applied split fee(scaled by 1e18) and the resulting CDP collateral amount after applied
591
    function getAccumulatedFeeSplitApplied(
592
        bytes32 _cdpId,
593
        uint256 _systemStEthFeePerUnitIndex
594
    ) public view returns (uint256, uint256) {
595
        uint256 _cdpStEthFeePerUnitIndex = cdpStEthFeePerUnitIndex[_cdpId];
596
        uint256 _cdpCol = Cdps[_cdpId].coll;
597

                            
                        
598
        if (
599
            _cdpStEthFeePerUnitIndex == 0 ||
600
            _cdpCol == 0 ||
601
            _cdpStEthFeePerUnitIndex == _systemStEthFeePerUnitIndex
602
        ) {
603
            return (0, _cdpCol);
604
        }
605

                            
                        
606
        uint256 _feeSplitDistributed = Cdps[_cdpId].stake *
607
            (_systemStEthFeePerUnitIndex - _cdpStEthFeePerUnitIndex);
608

                            
                        
609
        uint256 _scaledCdpColl = _cdpCol * DECIMAL_PRECISION;
610

                            
                        
611
        if (_scaledCdpColl > _feeSplitDistributed) {
612
            return (
613
                _feeSplitDistributed,
614
                (_scaledCdpColl - _feeSplitDistributed) / DECIMAL_PRECISION
615
            );
616
        } else {
617
            // extreme unlikely case to skip fee split on this CDP to avoid revert
618
            return (0, _cdpCol);
619
        }
620
    }
621

                            
                        
622
    // -- Modifier functions --
623
    function _requireCdpIsActive(bytes32 _cdpId) internal view {
624
        require(Cdps[_cdpId].status == Status.active, "CdpManager: Cdp does not exist or is closed");
625
    }
626

                            
                        
627
    function _requireMoreThanOneCdpInSystem(uint256 CdpOwnersArrayLength) internal view {
628
        require(
629
            CdpOwnersArrayLength > 1 && sortedCdps.getSize() > 1,
630
            "CdpManager: Only one cdp in the system"
631
        );
632
    }
633

                            
                        
634
    function _requireCallerIsBorrowerOperations() internal view {
635
        require(
636
            msg.sender == borrowerOperationsAddress,
637
            "CdpManager: Caller is not the BorrowerOperations contract"
638
        );
639
    }
640

                            
                        
641
    // --- Helper functions ---
642

                            
                        
643
    /// @notice Return the nominal collateral ratio (ICR) of a given Cdp, without the price.
644
    /// @dev Takes a cdp's pending coll and debt rewards from redistributions into account.
645
    function getNominalICR(bytes32 _cdpId) external view returns (uint256) {
646
        (uint256 currentEBTCDebt, uint256 currentCollShares, ) = getDebtAndCollShares(_cdpId);
647

                            
                        
648
        uint256 NICR = EbtcMath._computeNominalCR(currentCollShares, currentEBTCDebt);
649
        return NICR;
650
    }
651

                            
                        
652
    /// @notice Return the nominal collateral ratio (ICR) of a given Cdp, without the price.
653
    /// @dev Takes a cdp's pending coll and debt rewards as well as stETH Index into account.
654
    function getSyncedNominalICR(bytes32 _cdpId) external view returns (uint256) {
655
        (uint256 _oldIndex, uint256 _newIndex) = _readStEthIndex();
656
        (, uint256 _newGlobalSplitIdx, ) = _calcSyncedGlobalAccounting(_newIndex, _oldIndex);
657
        (uint256 _newColl, uint256 _newDebt, , uint256 _pendingDebt) = _calcSyncedAccounting(
658
            _cdpId,
659
            cdpStEthFeePerUnitIndex[_cdpId],
660
            _newGlobalSplitIdx /// NOTE: This is latest index
661
        );
662

                            
                        
663
        uint256 NICR = EbtcMath._computeNominalCR(_newColl, _newDebt);
664
        return NICR;
665
    }
666

                            
                        
667
    // Return the current collateral ratio (ICR) of a given Cdp.
668
    //Takes a cdp's pending coll and debt rewards from redistributions into account.
669
    function getICR(bytes32 _cdpId, uint256 _price) public view returns (uint256) {
670
        (uint256 currentEBTCDebt, uint256 currentCollShares, ) = getDebtAndCollShares(_cdpId);
671
        uint256 ICR = _calculateCR(currentCollShares, currentEBTCDebt, _price);
672
        return ICR;
673
    }
674

                            
                        
675
    function _calculateCR(
676
        uint256 currentCollShare,
677
        uint256 currentDebt,
678
        uint256 _price
679
    ) internal view returns (uint256) {
680
        uint256 _underlyingCollateral = collateral.getPooledEthByShares(currentCollShare);
681
        return EbtcMath._computeCR(_underlyingCollateral, currentDebt, _price);
682
    }
683

                            
                        
684
    /**
685
    get the pending Cdp debt "reward" (i.e. the amount of extra debt assigned to the Cdp) from liquidation redistribution events, earned by their stake
686
    */
687
    function getPendingRedistributedDebt(
688
        bytes32 _cdpId
689
    ) public view returns (uint256 pendingEBTCDebtReward) {
690
        return _getPendingRedistributedDebt(_cdpId);
691
    }
692

                            
                        
693
    function hasPendingRedistributedDebt(bytes32 _cdpId) public view returns (bool) {
694
        return _hasRedistributedDebt(_cdpId);
695
    }
696

                            
                        
697
    // Return the Cdps entire debt and coll struct
698
    function _getDebtAndCollShares(
699
        bytes32 _cdpId
700
    ) internal view returns (CdpDebtAndCollShares memory) {
701
        (uint256 entireDebt, uint256 entireColl, uint256 pendingDebtReward) = getDebtAndCollShares(
702
            _cdpId
703
        );
704
        return CdpDebtAndCollShares(entireDebt, entireColl, pendingDebtReward);
705
    }
706

                            
                        
707
    // Return the Cdps entire debt and coll, including pending rewards from redistributions and collateral reduction from split fee.
708
    /// @notice pending rewards are included in the debt and coll totals returned.
709
    function getDebtAndCollShares(
710
        bytes32 _cdpId
711
    ) public view returns (uint256 debt, uint256 coll, uint256 pendingEBTCDebtReward) {
712
        (uint256 _newColl, uint256 _newDebt, , uint256 _pendingDebt) = _calcSyncedAccounting(
713
            _cdpId,
714
            cdpStEthFeePerUnitIndex[_cdpId],
715
            systemStEthFeePerUnitIndex
716
        );
717
        coll = _newColl;
718
        debt = _newDebt;
719
        pendingEBTCDebtReward = _pendingDebt;
720
    }
721

                            
                        
722
    /// @dev calculate pending global state change to be applied:
723
    /// @return split fee taken (if any) AND
724
    /// @return new split index per stake unit AND
725
    /// @return new split index error
726
    function _calcSyncedGlobalAccounting(
727
        uint256 _newIndex,
728
        uint256 _oldIndex
729
    ) internal view returns (uint256, uint256, uint256) {
730
        if (_newIndex > _oldIndex && totalStakes > 0) {
731
            /// @audit-ok We don't take the fee if we had a negative rebase
732
            (
733
                uint256 _feeTaken,
734
                uint256 _deltaFeePerUnit,
735
                uint256 _perUnitError
736
            ) = calcFeeUponStakingReward(_newIndex, _oldIndex);
737

                            
                        
738
            // calculate new split per stake unit
739
            uint256 _newPerUnit = systemStEthFeePerUnitIndex + _deltaFeePerUnit;
740
            return (_feeTaken, _newPerUnit, _perUnitError);
741
        } else {
742
            return (0, systemStEthFeePerUnitIndex, systemStEthFeePerUnitIndexError);
743
        }
744
    }
745

                            
                        
746
    /// @dev calculate pending state change to be applied for given CDP and global split index(typically already synced):
747
    /// @return new CDP collateral share after pending change applied
748
    /// @return new CDP debt after pending change applied
749
    /// @return split fee applied to given CDP
750
    /// @return redistributed debt applied to given CDP
751
    function _calcSyncedAccounting(
752
        bytes32 _cdpId,
753
        uint256 _cdpPerUnitIdx,
754
        uint256 _systemStEthFeePerUnitIndex
755
    ) internal view returns (uint256, uint256, uint256, uint256) {
756
        uint256 _feeSplitApplied;
757
        uint256 _newCollShare = Cdps[_cdpId].coll;
758

                            
                        
759
        // processing split fee to be applied
760
        if (_cdpPerUnitIdx != _systemStEthFeePerUnitIndex && _cdpPerUnitIdx > 0) {
761
            (
762
                uint256 _feeSplitDistributed,
763
                uint256 _newCollShareAfter
764
            ) = getAccumulatedFeeSplitApplied(_cdpId, _systemStEthFeePerUnitIndex);
765
            _feeSplitApplied = _feeSplitDistributed;
766
            _newCollShare = _newCollShareAfter;
767
        }
768

                            
                        
769
        // processing redistributed debt to be applied
770
        (uint256 _newDebt, uint256 pendingDebtRedistributed) = _getSyncedCdpDebtAndRedistribution(
771
            _cdpId
772
        );
773

                            
                        
774
        return (_newCollShare, _newDebt, _feeSplitApplied, pendingDebtRedistributed);
775
    }
776

                            
                        
777
    /// @return CDP debt and pending redistribution from liquidation applied
778
    function _getSyncedCdpDebtAndRedistribution(
779
        bytes32 _cdpId
780
    ) internal view returns (uint256, uint256) {
781
        uint256 pendingDebtRedistributed = _getPendingRedistributedDebt(_cdpId);
782
        uint256 _newDebt = Cdps[_cdpId].debt;
783
        if (pendingDebtRedistributed > 0) {
784
            _newDebt = _newDebt + pendingDebtRedistributed;
785
        }
786
        return (_newDebt, pendingDebtRedistributed);
787
    }
788

                            
                        
789
    /// @return CDP debt with pending redistribution from liquidation applied
790
    function getSyncedCdpDebt(bytes32 _cdpId) public view returns (uint256) {
791
        (uint256 _newDebt, ) = _getSyncedCdpDebtAndRedistribution(_cdpId);
792
        return _newDebt;
793
    }
794

                            
                        
795
    /// @return CDP collateral with pending split fee applied
796
    function getSyncedCdpCollShares(bytes32 _cdpId) public view returns (uint256) {
797
        (uint256 _oldIndex, uint256 _newIndex) = _readStEthIndex();
798
        (, uint256 _newGlobalSplitIdx, ) = _calcSyncedGlobalAccounting(_newIndex, _oldIndex);
799
        (uint256 _newColl, , , ) = _calcSyncedAccounting(
800
            _cdpId,
801
            cdpStEthFeePerUnitIndex[_cdpId],
802
            _newGlobalSplitIdx
803
        );
804
        return _newColl;
805
    }
806

                            
                        
807
    /// @return CDP ICR with pending collateral and debt change applied
808
    function getSyncedICR(bytes32 _cdpId, uint256 _price) public view returns (uint256) {
809
        uint256 _debt = getSyncedCdpDebt(_cdpId);
810
        uint256 _collShare = getSyncedCdpCollShares(_cdpId);
811
        return _calculateCR(_collShare, _debt, _price);
812
    }
813

                            
                        
814
    /// @return TCR with pending collateral and debt change applied
815
    function getSyncedTCR(uint256 _price) public view returns (uint256) {
816
        (uint256 _oldIndex, uint256 _newIndex) = _readStEthIndex();
817
        (uint256 _feeTaken, , ) = _calcSyncedGlobalAccounting(_newIndex, _oldIndex);
818

                            
                        
819
        uint256 _systemCollShare = activePool.getSystemCollShares();
820
        if (_feeTaken > 0) {
821
            _systemCollShare = _systemCollShare - _feeTaken;
822
        }
823
        uint256 _systemDebt = activePool.getSystemDebt();
824
        return _calculateCR(_systemCollShare, _systemDebt, _price);
825
    }
826

                            
                        
827
    // Can liquidate in RM if ICR < TCR AND Enough time has passed
828
    function canLiquidateRecoveryMode(uint256 icr, uint256 tcr) public view returns (bool) {
829
        return _checkICRAgainstTCR(icr, tcr) && _recoveryModeGracePeriodPassed();
830
    }
831

                            
                        
832
    /// @dev Check if enough time has passed for grace period after enabled
833
    function _recoveryModeGracePeriodPassed() internal view returns (bool) {
834
        // we have waited enough
835
        uint128 cachedLastGracePeriodStartTimestamp = lastGracePeriodStartTimestamp;
836
        return
837
            cachedLastGracePeriodStartTimestamp != UNSET_TIMESTAMP &&
838
            block.timestamp > cachedLastGracePeriodStartTimestamp + recoveryModeGracePeriodDuration;
839
    }
840
}
841

                            
                        

Lines covered: 20 / 42 (47.6%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
import "./Interfaces/ICollSurplusPool.sol";
6
import "./Dependencies/ICollateralToken.sol";
7
import "./Dependencies/SafeERC20.sol";
8
import "./Dependencies/ReentrancyGuard.sol";
9
import "./Dependencies/AuthNoOwner.sol";
10
import "./Interfaces/IActivePool.sol";
11

                            
                        
12
contract CollSurplusPool is ICollSurplusPool, ReentrancyGuard, AuthNoOwner {
13
    using SafeERC20 for IERC20;
14

                            
                        
15
    string public constant NAME = "CollSurplusPool";
16

                            
                        
17
    address public immutable borrowerOperationsAddress;
18
    address public immutable cdpManagerAddress;
19
    address public immutable activePoolAddress;
20
    address public immutable feeRecipientAddress;
21
    ICollateralToken public immutable collateral;
22

                            
                        
23
    // deposited ether tracker
24
    uint256 internal totalSurplusCollShares;
25
    // Collateral surplus claimable by cdp owners
26
    mapping(address => uint256) internal balances;
27

                            
                        
28
    // --- Contract setters ---
29

                            
                        
30
    /**
31
     * @notice Sets the addresses of the contracts and renounces ownership
32
     * @dev One-time initialization function. Can only be called by the owner as a security measure. Ownership is renounced after the function is called.
33
     * @param _borrowerOperationsAddress The address of the BorrowerOperations
34
     * @param _cdpManagerAddress The address of the CDPManager
35
     * @param _activePoolAddress The address of the ActivePool
36
     * @param _collTokenAddress The address of the CollateralToken
37
     */
38
    constructor(
39
        address _borrowerOperationsAddress,
40
        address _cdpManagerAddress,
41
        address _activePoolAddress,
42
        address _collTokenAddress
43
    ) {
44
        borrowerOperationsAddress = _borrowerOperationsAddress;
45
        cdpManagerAddress = _cdpManagerAddress;
46
        activePoolAddress = _activePoolAddress;
47
        collateral = ICollateralToken(_collTokenAddress);
48
        feeRecipientAddress = IActivePool(activePoolAddress).feeRecipientAddress();
49

                            
                        
50
        address _authorityAddress = address(AuthNoOwner(cdpManagerAddress).authority());
51
        if (_authorityAddress != address(0)) {
52
            _initializeAuthority(_authorityAddress);
53
        }
54
    }
55

                            
                        
56
    /**
57
     * @notice Gets the current collateral state variable of the pool
58
     * @dev Not necessarily equal to the raw collateral token balance - tokens can be forcibly sent to contracts
59
     * @return The current collateral balance tracked by the variable
60
     */
61
    function getTotalSurplusCollShares() external view override returns (uint256) {
62
        return totalSurplusCollShares;
63
    }
64

                            
                        
65
    /**
66
     * @notice Gets the collateral surplus available for the given account
67
     * @param _account The address of the account
68
     * @return The collateral balance available to claim
69
     */
70
    function getSurplusCollShares(address _account) external view override returns (uint256) {
71
        return balances[_account];
72
    }
73

                            
                        
74
    // --- Pool functionality ---
75

                            
                        
76
    function increaseSurplusCollShares(address _account, uint256 _amount) external override {
77
        _requireCallerIsCdpManager();
78

                            
                        
79
        uint256 newAmount = balances[_account] + _amount;
80
        balances[_account] = newAmount;
81

                            
                        
82
        emit SurplusCollSharesUpdated(_account, newAmount);
83
    }
84

                            
                        
85
    function claimSurplusCollShares(address _account) external override {
86
        _requireCallerIsBorrowerOperations();
87
        uint256 claimableColl = balances[_account];
88
        require(claimableColl > 0, "CollSurplusPool: No collateral available to claim");
89

                            
                        
90
        balances[_account] = 0;
91
        emit SurplusCollSharesUpdated(_account, 0);
92

                            
                        
93
        uint256 cachedTotalSurplusCollShares = totalSurplusCollShares;
94

                            
                        
95
        require(cachedTotalSurplusCollShares >= claimableColl, "!CollSurplusPoolBal");
96
        // Safe per the check above
97
        unchecked {
98
            totalSurplusCollShares = cachedTotalSurplusCollShares - claimableColl;
99
        }
100
        emit CollSharesTransferred(_account, claimableColl);
101

                            
                        
102
        // NOTE: No need for safe transfer if the collateral asset is standard. Make sure this is the case!
103
        collateral.transferShares(_account, claimableColl);
104
    }
105

                            
                        
106
    // --- 'require' functions ---
107

                            
                        
108
    function _requireCallerIsBorrowerOperations() internal view {
109
        require(
110
            msg.sender == borrowerOperationsAddress,
111
            "CollSurplusPool: Caller is not Borrower Operations"
112
        );
113
    }
114

                            
                        
115
    function _requireCallerIsCdpManager() internal view {
116
        require(msg.sender == cdpManagerAddress, "CollSurplusPool: Caller is not CdpManager");
117
    }
118

                            
                        
119
    function _requireCallerIsActivePool() internal view {
120
        require(msg.sender == activePoolAddress, "CollSurplusPool: Caller is not Active Pool");
121
    }
122

                            
                        
123
    function increaseTotalSurplusCollShares(uint256 _value) external override {
124
        _requireCallerIsActivePool();
125
        totalSurplusCollShares = totalSurplusCollShares + _value;
126
    }
127

                            
                        
128
    // === Governed Functions === //
129

                            
                        
130
    /// @dev Function to move unintended dust that are not protected
131
    /// @notice moves given amount of given token (collateral is NOT allowed)
132
    /// @notice because recipient are fixed, this function is safe to be called by anyone
133
    function sweepToken(address token, uint256 amount) public nonReentrant requiresAuth {
134
        require(token != address(collateral), "CollSurplusPool: Cannot Sweep Collateral");
135

                            
                        
136
        uint256 balance = IERC20(token).balanceOf(address(this));
137
        require(amount <= balance, "CollSurplusPool: Attempt to sweep more than balance");
138

                            
                        
139
        IERC20(token).safeTransfer(feeRecipientAddress, amount);
140

                            
                        
141
        emit SweepTokenSuccess(token, amount, feeRecipientAddress);
142
    }
143
}
144

                            
                        

Lines covered: 0 / 19 (0.0%)

1
// SPDX-License-Identifier: MIT
2
// OpenZeppelin Contracts (last updated v4.8.0) (utils/Address.sol)
3

                            
                        
4
pragma solidity 0.8.17;
5

                            
                        
6
/**
7
 * @dev Collection of functions related to the address type
8
 */
9
library Address {
10
    /**
11
     * @dev Returns true if `account` is a contract.
12
     *
13
     * [IMPORTANT]
14
     * ====
15
     * It is unsafe to assume that an address for which this function returns
16
     * false is an externally-owned account (EOA) and not a contract.
17
     *
18
     * Among others, `isContract` will return false for the following
19
     * types of addresses:
20
     *
21
     *  - an externally-owned account
22
     *  - a contract in construction
23
     *  - an address where a contract will be created
24
     *  - an address where a contract lived, but was destroyed
25
     *
26
     * Furthermore, `isContract` will also return true if the target contract within
27
     * the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
28
     * which only has an effect at the end of a transaction.
29
     * ====
30
     *
31
     * [IMPORTANT]
32
     * ====
33
     * You shouldn't rely on `isContract` to protect against flash loan attacks!
34
     *
35
     * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
36
     * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
37
     * constructor.
38
     * ====
39
     */
40
    function isContract(address account) internal view returns (bool) {
41
        // This method relies on extcodesize/address.code.length, which returns 0
42
        // for contracts in construction, since the code is only stored at the end
43
        // of the constructor execution.
44

                            
                        
45
        return account.code.length > 0;
46
    }
47

                            
                        
48
    /**
49
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
50
     * `errorMessage` as a fallback revert reason when `target` reverts.
51
     *
52
     * _Available since v3.1._
53
     */
54
    function functionCall(
55
        address target,
56
        bytes memory data,
57
        string memory errorMessage
58
    ) internal returns (bytes memory) {
59
        return functionCallWithValue(target, data, 0, errorMessage);
60
    }
61

                            
                        
62
    /**
63
     * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
64
     * with `errorMessage` as a fallback revert reason when `target` reverts.
65
     *
66
     * _Available since v3.1._
67
     */
68
    function functionCallWithValue(
69
        address target,
70
        bytes memory data,
71
        uint256 value,
72
        string memory errorMessage
73
    ) internal returns (bytes memory) {
74
        require(address(this).balance >= value, "Address: insufficient balance for call");
75
        (bool success, bytes memory returndata) = target.call{value: value}(data);
76
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
77
    }
78

                            
                        
79
    /**
80
     * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
81
     * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
82
     *
83
     * _Available since v4.8._
84
     */
85
    function verifyCallResultFromTarget(
86
        address target,
87
        bool success,
88
        bytes memory returndata,
89
        string memory errorMessage
90
    ) internal view returns (bytes memory) {
91
        if (success) {
92
            if (returndata.length == 0) {
93
                // only check isContract if the call was successful and the return data is empty
94
                // otherwise we already know that it was a contract
95
                require(isContract(target), "Address: call to non-contract");
96
            }
97
            return returndata;
98
        } else {
99
            _revert(returndata, errorMessage);
100
        }
101
    }
102

                            
                        
103
    function _revert(bytes memory returndata, string memory errorMessage) private pure {
104
        // Look for revert reason and bubble it up if present
105
        if (returndata.length > 0) {
106
            // The easiest way to bubble the revert reason is using memory via assembly
107
            /// @solidity memory-safe-assembly
108
            assembly {
109
                let returndata_size := mload(returndata)
110
                revert(add(32, returndata), returndata_size)
111
            }
112
        } else {
113
            revert(errorMessage);
114
        }
115
    }
116
}
117

                            
                        

Lines covered: 9 / 16 (56.2%)

1
// SPDX-License-Identifier: AGPL-3.0-only
2
pragma solidity 0.8.17;
3

                            
                        
4
import {Authority} from "./Authority.sol";
5

                            
                        
6
/// @notice Provides a flexible and updatable auth pattern which is completely separate from application logic.
7
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/Auth.sol)
8
/// @author Modified from Dappsys (https://github.com/dapphub/ds-auth/blob/master/src/auth.sol)
9
abstract contract Auth {
10
    event OwnershipTransferred(address indexed user, address indexed newOwner);
11

                            
                        
12
    event AuthorityUpdated(address indexed user, Authority indexed newAuthority);
13

                            
                        
14
    address public owner;
15

                            
                        
16
    Authority public authority;
17

                            
                        
18
    constructor(address _owner, Authority _authority) {
19
        owner = _owner;
20
        authority = _authority;
21

                            
                        
22
        emit OwnershipTransferred(msg.sender, _owner);
23
        emit AuthorityUpdated(msg.sender, _authority);
24
    }
25

                            
                        
26
    modifier requiresAuth() virtual {
27
        require(isAuthorized(msg.sender, msg.sig), "Auth: UNAUTHORIZED");
28

                            
                        
29
        _;
30
    }
31

                            
                        
32
    function isAuthorized(address user, bytes4 functionSig) internal view virtual returns (bool) {
33
        Authority auth = authority; // Memoizing authority saves us a warm SLOAD, around 100 gas.
34

                            
                        
35
        // Checking if the caller is the owner only after calling the authority saves gas in most cases, but be
36
        // aware that this makes protected functions uncallable even to the owner if the authority is out of order.
37
        return
38
            (address(auth) != address(0) && auth.canCall(user, address(this), functionSig)) ||
39
            user == owner;
40
    }
41

                            
                        
42
    function setAuthority(Authority newAuthority) public virtual {
43
        // We check if the caller is the owner first because we want to ensure they can
44
        // always swap out the authority even if it's reverting or using up a lot of gas.
45
        require(msg.sender == owner || authority.canCall(msg.sender, address(this), msg.sig));
46

                            
                        
47
        authority = newAuthority;
48

                            
                        
49
        emit AuthorityUpdated(msg.sender, newAuthority);
50
    }
51

                            
                        
52
    function transferOwnership(address newOwner) public virtual requiresAuth {
53
        owner = newOwner;
54

                            
                        
55
        emit OwnershipTransferred(msg.sender, newOwner);
56
    }
57
}
58

                            
                        

Lines covered: 11 / 19 (57.9%)

1
// SPDX-License-Identifier: AGPL-3.0-only
2
pragma solidity 0.8.17;
3

                            
                        
4
import {Authority} from "./Authority.sol";
5

                            
                        
6
/// @notice Provides a flexible and updatable auth pattern which is completely separate from application logic.
7
/// @author Modified by BadgerDAO to remove owner
8
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/Auth.sol)
9
/// @author Modified from Dappsys (https://github.com/dapphub/ds-auth/blob/master/src/auth.sol)
10
contract AuthNoOwner {
11
    event AuthorityUpdated(address indexed user, Authority indexed newAuthority);
12

                            
                        
13
    Authority private _authority;
14
    bool private _authorityInitialized;
15

                            
                        
16
    modifier requiresAuth() virtual {
17
        require(isAuthorized(msg.sender, msg.sig), "Auth: UNAUTHORIZED");
18

                            
                        
19
        _;
20
    }
21

                            
                        
22
    function authority() public view returns (Authority) {
23
        return _authority;
24
    }
25

                            
                        
26
    function authorityInitialized() public view returns (bool) {
27
        return _authorityInitialized;
28
    }
29

                            
                        
30
    function isAuthorized(address user, bytes4 functionSig) internal view virtual returns (bool) {
31
        Authority auth = _authority; // Memoizing authority saves us a warm SLOAD, around 100 gas.
32

                            
                        
33
        // Checking if the caller is the owner only after calling the authority saves gas in most cases, but be
34
        // aware that this makes protected functions uncallable even to the owner if the authority is out of order.
35
        return (address(auth) != address(0) && auth.canCall(user, address(this), functionSig));
36
    }
37

                            
                        
38
    function setAuthority(address newAuthority) public virtual {
39
        // We check if the caller is the owner first because we want to ensure they can
40
        // always swap out the authority even if it's reverting or using up a lot of gas.
41
        require(_authority.canCall(msg.sender, address(this), msg.sig));
42

                            
                        
43
        _authority = Authority(newAuthority);
44

                            
                        
45
        // Once authority is set once via any means, ensure it is initialized
46
        if (!_authorityInitialized) {
47
            _authorityInitialized = true;
48
        }
49

                            
                        
50
        emit AuthorityUpdated(msg.sender, Authority(newAuthority));
51
    }
52

                            
                        
53
    /// @notice Changed constructor to initialize to allow flexiblity of constructor vs initializer use
54
    /// @notice sets authorityInitiailzed flag to ensure only one use of
55
    function _initializeAuthority(address newAuthority) internal {
56
        require(address(_authority) == address(0), "Auth: authority is non-zero");
57
        require(!_authorityInitialized, "Auth: authority already initialized");
58

                            
                        
59
        _authority = Authority(newAuthority);
60
        _authorityInitialized = true;
61

                            
                        
62
        emit AuthorityUpdated(address(this), Authority(newAuthority));
63
    }
64
}
65

                            
                        

Lines covered: 0 / 0 (0.0%)

1
// SPDX-License-Identifier: AGPL-3.0-only
2
pragma solidity 0.8.17;
3

                            
                        
4
/// @notice A generic interface for a contract which provides authorization data to an Auth instance.
5
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/Auth.sol)
6
/// @author Modified from Dappsys (https://github.com/dapphub/ds-auth/blob/master/src/auth.sol)
7
interface Authority {
8
    function canCall(address user, address target, bytes4 functionSig) external view returns (bool);
9
}
10

                            
                        

Lines covered: 1 / 2 (50.0%)

1
// SPDX-License-Identifier: MIT
2
pragma solidity 0.8.17;
3

                            
                        
4
contract BaseMath {
5
    uint256 public constant DECIMAL_PRECISION = 1e18;
6
}
7

                            
                        

Lines covered: 2 / 2 (100.0%)

1
// SPDX-License-Identifier: MIT
2
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
3

                            
                        
4
pragma solidity 0.8.17;
5

                            
                        
6
/**
7
 * @dev Provides information about the current execution context, including the
8
 * sender of the transaction and its data. While these are generally available
9
 * via msg.sender and msg.data, they should not be accessed in such a direct
10
 * manner, since when dealing with meta-transactions the account sending and
11
 * paying for execution may not be the actual sender (as far as an application
12
 * is concerned).
13
 *
14
 * This contract is only required for intermediate, library-like contracts.
15
 */
16
abstract contract Context {
17
    function _msgSender() internal view virtual returns (address) {
18
        return msg.sender;
19
    }
20

                            
                        
21
    function _msgData() internal view virtual returns (bytes calldata) {
22
        return msg.data;
23
    }
24
}
25

                            
                        

Lines covered: 20 / 21 (95.2%)

1
//SPDX-License-Identifier: Unlicense
2
pragma solidity 0.8.17;
3

                            
                        
4
/**
5
  @title A library for deploying contracts EIP-3171 style.
6
  @author Agustin Aguilar <aa@horizon.io>
7
*/
8
library Create3 {
9
    error ErrorCreatingProxy();
10
    error ErrorCreatingContract();
11
    error TargetAlreadyExists();
12

                            
                        
13
    /**
14
    @notice The bytecode for a contract that proxies the creation of another contract
15
    @dev If this code is deployed using CREATE2 it can be used to decouple `creationCode` from the child contract address
16

                            
                        
17
  0x67363d3d37363d34f03d5260086018f3:
18
      0x00  0x67  0x67XXXXXXXXXXXXXXXX  PUSH8 bytecode  0x363d3d37363d34f0
19
      0x01  0x3d  0x3d                  RETURNDATASIZE  0 0x363d3d37363d34f0
20
      0x02  0x52  0x52                  MSTORE
21
      0x03  0x60  0x6008                PUSH1 08        8
22
      0x04  0x60  0x6018                PUSH1 18        24 8
23
      0x05  0xf3  0xf3                  RETURN
24

                            
                        
25
  0x363d3d37363d34f0:
26
      0x00  0x36  0x36                  CALLDATASIZE    cds
27
      0x01  0x3d  0x3d                  RETURNDATASIZE  0 cds
28
      0x02  0x3d  0x3d                  RETURNDATASIZE  0 0 cds
29
      0x03  0x37  0x37                  CALLDATACOPY
30
      0x04  0x36  0x36                  CALLDATASIZE    cds
31
      0x05  0x3d  0x3d                  RETURNDATASIZE  0 cds
32
      0x06  0x34  0x34                  CALLVALUE       val 0 cds
33
      0x07  0xf0  0xf0                  CREATE          addr
34
  */
35

                            
                        
36
    bytes internal constant PROXY_CHILD_BYTECODE =
37
        hex"67_36_3d_3d_37_36_3d_34_f0_3d_52_60_08_60_18_f3";
38

                            
                        
39
    //                        KECCAK256_PROXY_CHILD_BYTECODE = keccak256(PROXY_CHILD_BYTECODE);
40
    bytes32 internal constant KECCAK256_PROXY_CHILD_BYTECODE =
41
        0x21c35dbe1b344a2488cf3321d6ce542f8e9f305544ff09e4993a62319a497c1f;
42

                            
                        
43
    /**
44
    @notice Returns the size of the code on a given address
45
    @param _addr Address that may or may not contain code
46
    @return size of the code on the given `_addr`
47
  */
48
    function codeSize(address _addr) internal view returns (uint256 size) {
49
        assembly {
50
            size := extcodesize(_addr)
51
        }
52
    }
53

                            
                        
54
    /**
55
    @notice Creates a new contract with given `_creationCode` and `_salt`
56
    @param _salt Salt of the contract creation, resulting address will be derivated from this value only
57
    @param _creationCode Creation code (constructor) of the contract to be deployed, this value doesn't affect the resulting address
58
    @return addr of the deployed contract, reverts on error
59
  */
60
    function create3(bytes32 _salt, bytes memory _creationCode) internal returns (address addr) {
61
        return create3(_salt, _creationCode, 0);
62
    }
63

                            
                        
64
    /**
65
    @notice Creates a new contract with given `_creationCode` and `_salt`
66
    @param _salt Salt of the contract creation, resulting address will be derivated from this value only
67
    @param _creationCode Creation code (constructor) of the contract to be deployed, this value doesn't affect the resulting address
68
    @param _value In WEI of ETH to be forwarded to child contract
69
    @return addr of the deployed contract, reverts on error
70
  */
71
    function create3(
72
        bytes32 _salt,
73
        bytes memory _creationCode,
74
        uint256 _value
75
    ) internal returns (address addr) {
76
        // Creation code
77
        bytes memory creationCode = PROXY_CHILD_BYTECODE;
78

                            
                        
79
        // Get target final address
80
        addr = addressOf(_salt);
81
        if (codeSize(addr) != 0) revert TargetAlreadyExists();
82

                            
                        
83
        // Create CREATE2 proxy
84
        address proxy;
85
        assembly {
86
            proxy := create2(0, add(creationCode, 32), mload(creationCode), _salt)
87
        }
88
        if (proxy == address(0)) revert ErrorCreatingProxy();
89

                            
                        
90
        // Call proxy with final init code
91
        (bool success, ) = proxy.call{value: _value}(_creationCode);
92
        if (!success || codeSize(addr) == 0) revert ErrorCreatingContract();
93
    }
94

                            
                        
95
    /**
96
    @notice Computes the resulting address of a contract deployed using address(this) and the given `_salt`
97
    @param _salt Salt of the contract creation, resulting address will be derivated from this value only
98
    @return addr of the deployed contract, reverts on error
99

                            
                        
100
    @dev The address creation formula is: keccak256(rlp([keccak256(0xff ++ address(this) ++ _salt ++ keccak256(childBytecode))[12:], 0x01]))
101
  */
102
    function addressOf(bytes32 _salt) internal view returns (address) {
103
        address proxy = address(
104
            uint160(
105
                uint256(
106
                    keccak256(
107
                        abi.encodePacked(
108
                            hex"ff",
109
                            address(this),
110
                            _salt,
111
                            KECCAK256_PROXY_CHILD_BYTECODE
112
                        )
113
                    )
114
                )
115
            )
116
        );
117

                            
                        
118
        return address(uint160(uint256(keccak256(abi.encodePacked(hex"d6_94", proxy, hex"01")))));
119
    }
120
}
121

                            
                        

Lines covered: 1 / 5 (20.0%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
import "../Interfaces/IERC3156FlashLender.sol";
6
import "../Interfaces/IWETH.sol";
7

                            
                        
8
abstract contract ERC3156FlashLender is IERC3156FlashLender {
9
    // TODO: Fix / Finalize
10
    uint256 public constant MAX_BPS = 10_000;
11
    uint256 public constant MAX_FEE_BPS = 1_000; // 10%
12
    bytes32 public constant FLASH_SUCCESS_VALUE = keccak256("ERC3156FlashBorrower.onFlashLoan");
13

                            
                        
14
    // Functions to modify these variables must be included in impelemnting contracts if desired
15
    uint16 public feeBps = 3; // may be subject to future adjustments through protocol governance
16
    bool public flashLoansPaused;
17
}
18

                            
                        

Lines covered: 39 / 44 (88.6%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
import "./BaseMath.sol";
6
import "./EbtcMath.sol";
7
import "../Interfaces/IActivePool.sol";
8
import "../Interfaces/IPriceFeed.sol";
9
import "../Interfaces/IEbtcBase.sol";
10
import "../Dependencies/ICollateralToken.sol";
11

                            
                        
12
/*
13
 * Base contract for CdpManager, BorrowerOperations. Contains global system constants and
14
 * common functions.
15
 */
16
contract EbtcBase is BaseMath, IEbtcBase {
17
    // Collateral Ratio applied for Liquidation Incentive
18
    // i.e., liquidator repay $1 worth of debt to get back $1.03 worth of collateral
19
    uint256 public constant LICR = 1030000000000000000; // 103%
20

                            
                        
21
    // Minimum collateral ratio for individual cdps
22
    uint256 public constant MCR = 1100000000000000000; // 110%
23

                            
                        
24
    // Critical system collateral ratio. If the system's total collateral ratio (TCR) falls below the CCR, Recovery Mode is triggered.
25
    uint256 public constant CCR = 1250000000000000000; // 125%
26

                            
                        
27
    // Amount of stETH collateral to be locked in active pool on opening cdps
28
    uint256 public constant LIQUIDATOR_REWARD = 2e17;
29

                            
                        
30
    // Minimum amount of stETH collateral a CDP must have
31
    uint256 public constant MIN_NET_COLL = 2e18;
32

                            
                        
33
    uint256 public constant PERCENT_DIVISOR = 200; // dividing by 200 yields 0.5%
34

                            
                        
35
    uint256 public constant BORROWING_FEE_FLOOR = 0; // 0.5%
36

                            
                        
37
    uint256 public constant STAKING_REWARD_SPLIT = 5_000; // taking 50% cut from staking reward
38

                            
                        
39
    uint256 public constant MAX_REWARD_SPLIT = 10_000;
40

                            
                        
41
    IActivePool public immutable activePool;
42

                            
                        
43
    IPriceFeed public immutable override priceFeed;
44

                            
                        
45
    // the only collateral token allowed in CDP
46
    ICollateralToken public immutable collateral;
47

                            
                        
48
    constructor(address _activePoolAddress, address _priceFeedAddress, address _collateralAddress) {
49
        activePool = IActivePool(_activePoolAddress);
50
        priceFeed = IPriceFeed(_priceFeedAddress);
51
        collateral = ICollateralToken(_collateralAddress);
52
    }
53

                            
                        
54
    // --- Gas compensation functions ---
55

                            
                        
56
    function _getNetColl(uint256 _coll) internal pure returns (uint256) {
57
        return _coll - LIQUIDATOR_REWARD;
58
    }
59

                            
                        
60
    /**
61
        @notice Get the entire system collateral
62
        @notice Entire system collateral = collateral stored in ActivePool, using their internal accounting
63
        @dev Coll stored for liquidator rewards or coll in CollSurplusPool are not included
64
     */
65
    function getSystemCollShares() public view returns (uint256 entireSystemColl) {
66
        return (activePool.getSystemCollShares());
67
    }
68

                            
                        
69
    /**
70
        @notice Get the entire system debt
71
        @notice Entire system collateral = collateral stored in ActivePool, using their internal accounting
72
     */
73
    function _getSystemDebt() internal view returns (uint256 entireSystemDebt) {
74
        return (activePool.getSystemDebt());
75
    }
76

                            
                        
77
    function _getTCR(uint256 _price) internal view returns (uint256 TCR) {
78
        (TCR, , ) = _getTCRWithSystemDebtAndCollShares(_price);
79
    }
80

                            
                        
81
    function _getTCRWithSystemDebtAndCollShares(
82
        uint256 _price
83
    ) internal view returns (uint256 TCR, uint256 _coll, uint256 _debt) {
84
        uint256 systemCollShares = getSystemCollShares();
85
        uint256 systemDebt = _getSystemDebt();
86

                            
                        
87
        uint256 _systemStEthBalance = collateral.getPooledEthByShares(systemCollShares);
88
        TCR = EbtcMath._computeCR(_systemStEthBalance, systemDebt, _price);
89

                            
                        
90
        return (TCR, systemCollShares, systemDebt);
91
    }
92

                            
                        
93
    function _checkRecoveryMode(uint256 _price) internal view returns (bool) {
94
        return _checkRecoveryModeForTCR(_getTCR(_price));
95
    }
96

                            
                        
97
    function _checkRecoveryModeForTCR(uint256 _tcr) internal view returns (bool) {
98
        return _tcr < CCR;
99
    }
100

                            
                        
101
    function _requireUserAcceptsFee(
102
        uint256 _fee,
103
        uint256 _amount,
104
        uint256 _maxFeePercentage
105
    ) internal pure {
106
        uint256 feePercentage = (_fee * DECIMAL_PRECISION) / _amount;
107
        require(feePercentage <= _maxFeePercentage, "Fee exceeded provided maximum");
108
    }
109

                            
                        
110
    // Convert debt denominated in ETH to debt denominated in BTC given that _price is ETH/BTC
111
    // _debt is denominated in ETH
112
    // _price is ETH/BTC
113
    function _convertDebtDenominationToBtc(
114
        uint256 _debt,
115
        uint256 _price
116
    ) internal pure returns (uint256) {
117
        return (_debt * _price) / DECIMAL_PRECISION;
118
    }
119

                            
                        
120
    /// @dev return true if given ICR is qualified for liquidation compared to configured threshold
121
    /// @dev this function ONLY checks numbers not check grace period switch for Recovery Mode
122
    function _checkICRAgainstLiqThreshold(uint256 _icr, uint _tcr) internal view returns (bool) {
123
        // Either undercollateralized
124
        // OR, it's RM AND they meet the requirement
125
        // Swapped Requirement && RM to save gas
126
        return
127
            _checkICRAgainstMCR(_icr) ||
128
            (_checkICRAgainstTCR(_icr, _tcr) && _checkRecoveryModeForTCR(_tcr));
129
    }
130

                            
                        
131
    /// @dev return true if given ICR is qualified for liquidation compared to MCR
132
    function _checkICRAgainstMCR(uint256 _icr) internal view returns (bool) {
133
        return _icr < MCR;
134
    }
135

                            
                        
136
    /// @dev return true if given ICR is qualified for liquidation compared to TCR
137
    /// @dev typically used in Recovery Mode
138
    function _checkICRAgainstTCR(uint256 _icr, uint _tcr) internal view returns (bool) {
139
        /// @audit is _icr <= _tcr more dangerous for overal system safety?
140
        /// @audit Should we use _icr < CCR to allow any risky CDP being liquidated?
141
        return _icr <= _tcr;
142
    }
143
}
144

                            
                        

Lines covered: 31 / 36 (86.1%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
library EbtcMath {
6
    uint256 internal constant DECIMAL_PRECISION = 1e18;
7
    uint256 public constant MAX_TCR = type(uint256).max;
8

                            
                        
9
    /* Precision for Nominal ICR (independent of price). Rationale for the value:
10
     *
11
     * - Making it “too high” could lead to overflows.
12
     * - Making it “too low” could lead to an ICR equal to zero, due to truncation from Solidity floor division.
13
     *
14
     * This value of 1e20 is chosen for safety: the NICR will only overflow for numerator > ~1e39 ETH,
15
     * and will only truncate to 0 if the denominator is at least 1e20 times greater than the numerator.
16
     *
17
     */
18
    uint256 internal constant NICR_PRECISION = 1e20;
19

                            
                        
20
    function _min(uint256 _a, uint256 _b) internal pure returns (uint256) {
21
        return (_a < _b) ? _a : _b;
22
    }
23

                            
                        
24
    function _max(uint256 _a, uint256 _b) internal pure returns (uint256) {
25
        return (_a >= _b) ? _a : _b;
26
    }
27

                            
                        
28
    /*
29
     * Multiply two decimal numbers and use normal rounding rules:
30
     * -round product up if 19'th mantissa digit >= 5
31
     * -round product down if 19'th mantissa digit < 5
32
     *
33
     * Used only inside the exponentiation, _decPow().
34
     */
35
    function decMul(uint256 x, uint256 y) internal pure returns (uint256 decProd) {
36
        uint256 prod_xy = x * y;
37

                            
                        
38
        decProd = (prod_xy + (DECIMAL_PRECISION / 2)) / DECIMAL_PRECISION;
39
    }
40

                            
                        
41
    /*
42
     * _decPow: Exponentiation function for 18-digit decimal base, and integer exponent n.
43
     *
44
     * Uses the efficient "exponentiation by squaring" algorithm. O(log(n)) complexity.
45
     *
46
     * Called by two functions that represent time in units of minutes:
47
     * 1) CdpManager._calcDecayedBaseRate
48
     * 2) CommunityIssuance._getCumulativeIssuanceFraction
49
     *
50
     * The exponent is capped to avoid reverting due to overflow. The cap 525600000 equals
51
     * "minutes in 1000 years": 60 * 24 * 365 * 1000
52
     *
53
     * If a period of > 1000 years is ever used as an exponent in either of the above functions, the result will be
54
     * negligibly different from just passing the cap, since:
55
     *
56
     * In function 1), the decayed base rate will be 0 for 1000 years or > 1000 years
57
     * In function 2), the difference in tokens issued at 1000 years and any time > 1000 years, will be negligible
58
     */
59
    function _decPow(uint256 _base, uint256 _minutes) internal pure returns (uint256) {
60
        if (_minutes > 525600000) {
61
            _minutes = 525600000;
62
        } // cap to avoid overflow
63

                            
                        
64
        if (_minutes == 0) {
65
            return DECIMAL_PRECISION;
66
        }
67

                            
                        
68
        uint256 y = DECIMAL_PRECISION;
69
        uint256 x = _base;
70
        uint256 n = _minutes;
71

                            
                        
72
        // Exponentiation-by-squaring
73
        while (n > 1) {
74
            if (n % 2 == 0) {
75
                x = decMul(x, x);
76
                n = n / 2;
77
            } else {
78
                // if (n % 2 != 0)
79
                y = decMul(x, y);
80
                x = decMul(x, x);
81
                n = (n - 1) / 2;
82
            }
83
        }
84

                            
                        
85
        return decMul(x, y);
86
    }
87

                            
                        
88
    function _getAbsoluteDifference(uint256 _a, uint256 _b) internal pure returns (uint256) {
89
        return (_a >= _b) ? (_a - _b) : (_b - _a);
90
    }
91

                            
                        
92
    function _computeNominalCR(uint256 _collShares, uint256 _debt) internal pure returns (uint256) {
93
        if (_debt > 0) {
94
            return (_collShares * NICR_PRECISION) / _debt;
95
        }
96
        // Return the maximal value for uint256 if the Cdp has a debt of 0. Represents "infinite" CR.
97
        else {
98
            // if (_debt == 0)
99
            return MAX_TCR;
100
        }
101
    }
102

                            
                        
103
    /// @dev Compute collateralization ratio, given stETH balance, price, and debt balance
104
    function _computeCR(
105
        uint256 _stEthBalance,
106
        uint256 _debt,
107
        uint256 _price
108
    ) internal pure returns (uint256) {
109
        if (_debt > 0) {
110
            uint256 newCollRatio = (_stEthBalance * _price) / _debt;
111

                            
                        
112
            return newCollRatio;
113
        }
114
        // Return the maximal value for uint256 if the Cdp has a debt of 0. Represents "infinite" CR.
115
        else {
116
            // if (_debt == 0)
117
            return MAX_TCR;
118
        }
119
    }
120
}
121

                            
                        

Lines covered: 13 / 54 (24.1%)

1
// SPDX-License-Identifier: MIT
2
// OpenZeppelin Contracts (last updated v4.8.0) (utils/structs/EnumerableSet.sol)
3
// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.
4

                            
                        
5
pragma solidity 0.8.17;
6

                            
                        
7
/**
8
 * @dev Library for managing
9
 * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
10
 * types.
11
 *
12
 * Sets have the following properties:
13
 *
14
 * - Elements are added, removed, and checked for existence in constant time
15
 * (O(1)).
16
 * - Elements are enumerated in O(n). No guarantees are made on the ordering.
17
 *
18
 * ```solidity
19
 * contract Example {
20
 *     // Add the library methods
21
 *     using EnumerableSet for EnumerableSet.AddressSet;
22
 *
23
 *     // Declare a set state variable
24
 *     EnumerableSet.AddressSet private mySet;
25
 * }
26
 * ```
27
 *
28
 * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
29
 * and `uint256` (`UintSet`) are supported.
30
 *
31
 * [WARNING]
32
 * ====
33
 * Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
34
 * unusable.
35
 * See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
36
 *
37
 * In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an
38
 * array of EnumerableSet.
39
 * ====
40
 */
41
library EnumerableSet {
42
    // To implement this library for multiple types with as little code
43
    // repetition as possible, we write it in terms of a generic Set type with
44
    // bytes32 values.
45
    // The Set implementation uses private functions, and user-facing
46
    // implementations (such as AddressSet) are just wrappers around the
47
    // underlying Set.
48
    // This means that we can only create new EnumerableSets for types that fit
49
    // in bytes32.
50

                            
                        
51
    struct Set {
52
        // Storage of set values
53
        bytes32[] _values;
54
        // Position of the value in the `values` array, plus 1 because index 0
55
        // means a value is not in the set.
56
        mapping(bytes32 => uint256) _indexes;
57
    }
58

                            
                        
59
    /**
60
     * @dev Add a value to a set. O(1).
61
     *
62
     * Returns true if the value was added to the set, that is if it was not
63
     * already present.
64
     */
65
    function _add(Set storage set, bytes32 value) private returns (bool) {
66
        if (!_contains(set, value)) {
67
            set._values.push(value);
68
            // The value is stored at length-1, but we add 1 to all indexes
69
            // and use 0 as a sentinel value
70
            set._indexes[value] = set._values.length;
71
            return true;
72
        } else {
73
            return false;
74
        }
75
    }
76

                            
                        
77
    /**
78
     * @dev Removes a value from a set. O(1).
79
     *
80
     * Returns true if the value was removed from the set, that is if it was
81
     * present.
82
     */
83
    function _remove(Set storage set, bytes32 value) private returns (bool) {
84
        // We read and store the value's index to prevent multiple reads from the same storage slot
85
        uint256 valueIndex = set._indexes[value];
86

                            
                        
87
        if (valueIndex != 0) {
88
            // Equivalent to contains(set, value)
89
            // To delete an element from the _values array in O(1), we swap the element to delete with the last one in
90
            // the array, and then remove the last element (sometimes called as 'swap and pop').
91
            // This modifies the order of the array, as noted in {at}.
92

                            
                        
93
            uint256 toDeleteIndex = valueIndex - 1;
94
            uint256 lastIndex = set._values.length - 1;
95

                            
                        
96
            if (lastIndex != toDeleteIndex) {
97
                bytes32 lastValue = set._values[lastIndex];
98

                            
                        
99
                // Move the last value to the index where the value to delete is
100
                set._values[toDeleteIndex] = lastValue;
101
                // Update the index for the moved value
102
                set._indexes[lastValue] = valueIndex; // Replace lastValue's index to valueIndex
103
            }
104

                            
                        
105
            // Delete the slot where the moved value was stored
106
            set._values.pop();
107

                            
                        
108
            // Delete the index for the deleted slot
109
            delete set._indexes[value];
110

                            
                        
111
            return true;
112
        } else {
113
            return false;
114
        }
115
    }
116

                            
                        
117
    /**
118
     * @dev Returns true if the value is in the set. O(1).
119
     */
120
    function _contains(Set storage set, bytes32 value) private view returns (bool) {
121
        return set._indexes[value] != 0;
122
    }
123

                            
                        
124
    /**
125
     * @dev Returns the number of values on the set. O(1).
126
     */
127
    function _length(Set storage set) private view returns (uint256) {
128
        return set._values.length;
129
    }
130

                            
                        
131
    /**
132
     * @dev Returns the value stored at position `index` in the set. O(1).
133
     *
134
     * Note that there are no guarantees on the ordering of values inside the
135
     * array, and it may change when more values are added or removed.
136
     *
137
     * Requirements:
138
     *
139
     * - `index` must be strictly less than {length}.
140
     */
141
    function _at(Set storage set, uint256 index) private view returns (bytes32) {
142
        return set._values[index];
143
    }
144

                            
                        
145
    /**
146
     * @dev Return the entire set in an array
147
     *
148
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
149
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
150
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
151
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
152
     */
153
    function _values(Set storage set) private view returns (bytes32[] memory) {
154
        return set._values;
155
    }
156

                            
                        
157
    // Bytes32Set
158

                            
                        
159
    struct Bytes32Set {
160
        Set _inner;
161
    }
162

                            
                        
163
    /**
164
     * @dev Add a value to a set. O(1).
165
     *
166
     * Returns true if the value was added to the set, that is if it was not
167
     * already present.
168
     */
169
    function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
170
        return _add(set._inner, value);
171
    }
172

                            
                        
173
    /**
174
     * @dev Removes a value from a set. O(1).
175
     *
176
     * Returns true if the value was removed from the set, that is if it was
177
     * present.
178
     */
179
    function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
180
        return _remove(set._inner, value);
181
    }
182

                            
                        
183
    /**
184
     * @dev Returns true if the value is in the set. O(1).
185
     */
186
    function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
187
        return _contains(set._inner, value);
188
    }
189

                            
                        
190
    /**
191
     * @dev Returns the number of values in the set. O(1).
192
     */
193
    function length(Bytes32Set storage set) internal view returns (uint256) {
194
        return _length(set._inner);
195
    }
196

                            
                        
197
    /**
198
     * @dev Returns the value stored at position `index` in the set. O(1).
199
     *
200
     * Note that there are no guarantees on the ordering of values inside the
201
     * array, and it may change when more values are added or removed.
202
     *
203
     * Requirements:
204
     *
205
     * - `index` must be strictly less than {length}.
206
     */
207
    function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
208
        return _at(set._inner, index);
209
    }
210

                            
                        
211
    /**
212
     * @dev Return the entire set in an array
213
     *
214
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
215
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
216
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
217
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
218
     */
219
    function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
220
        bytes32[] memory store = _values(set._inner);
221
        bytes32[] memory result;
222

                            
                        
223
        /// @solidity memory-safe-assembly
224
        assembly {
225
            result := store
226
        }
227

                            
                        
228
        return result;
229
    }
230

                            
                        
231
    // AddressSet
232

                            
                        
233
    struct AddressSet {
234
        Set _inner;
235
    }
236

                            
                        
237
    /**
238
     * @dev Add a value to a set. O(1).
239
     *
240
     * Returns true if the value was added to the set, that is if it was not
241
     * already present.
242
     */
243
    function add(AddressSet storage set, address value) internal returns (bool) {
244
        return _add(set._inner, bytes32(uint256(uint160(value))));
245
    }
246

                            
                        
247
    /**
248
     * @dev Removes a value from a set. O(1).
249
     *
250
     * Returns true if the value was removed from the set, that is if it was
251
     * present.
252
     */
253
    function remove(AddressSet storage set, address value) internal returns (bool) {
254
        return _remove(set._inner, bytes32(uint256(uint160(value))));
255
    }
256

                            
                        
257
    /**
258
     * @dev Returns true if the value is in the set. O(1).
259
     */
260
    function contains(AddressSet storage set, address value) internal view returns (bool) {
261
        return _contains(set._inner, bytes32(uint256(uint160(value))));
262
    }
263

                            
                        
264
    /**
265
     * @dev Returns the number of values in the set. O(1).
266
     */
267
    function length(AddressSet storage set) internal view returns (uint256) {
268
        return _length(set._inner);
269
    }
270

                            
                        
271
    /**
272
     * @dev Returns the value stored at position `index` in the set. O(1).
273
     *
274
     * Note that there are no guarantees on the ordering of values inside the
275
     * array, and it may change when more values are added or removed.
276
     *
277
     * Requirements:
278
     *
279
     * - `index` must be strictly less than {length}.
280
     */
281
    function at(AddressSet storage set, uint256 index) internal view returns (address) {
282
        return address(uint160(uint256(_at(set._inner, index))));
283
    }
284

                            
                        
285
    /**
286
     * @dev Return the entire set in an array
287
     *
288
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
289
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
290
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
291
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
292
     */
293
    function values(AddressSet storage set) internal view returns (address[] memory) {
294
        bytes32[] memory store = _values(set._inner);
295
        address[] memory result;
296

                            
                        
297
        /// @solidity memory-safe-assembly
298
        assembly {
299
            result := store
300
        }
301

                            
                        
302
        return result;
303
    }
304

                            
                        
305
    // UintSet
306

                            
                        
307
    struct UintSet {
308
        Set _inner;
309
    }
310

                            
                        
311
    /**
312
     * @dev Add a value to a set. O(1).
313
     *
314
     * Returns true if the value was added to the set, that is if it was not
315
     * already present.
316
     */
317
    function add(UintSet storage set, uint256 value) internal returns (bool) {
318
        return _add(set._inner, bytes32(value));
319
    }
320

                            
                        
321
    /**
322
     * @dev Removes a value from a set. O(1).
323
     *
324
     * Returns true if the value was removed from the set, that is if it was
325
     * present.
326
     */
327
    function remove(UintSet storage set, uint256 value) internal returns (bool) {
328
        return _remove(set._inner, bytes32(value));
329
    }
330

                            
                        
331
    /**
332
     * @dev Returns true if the value is in the set. O(1).
333
     */
334
    function contains(UintSet storage set, uint256 value) internal view returns (bool) {
335
        return _contains(set._inner, bytes32(value));
336
    }
337

                            
                        
338
    /**
339
     * @dev Returns the number of values in the set. O(1).
340
     */
341
    function length(UintSet storage set) internal view returns (uint256) {
342
        return _length(set._inner);
343
    }
344

                            
                        
345
    /**
346
     * @dev Returns the value stored at position `index` in the set. O(1).
347
     *
348
     * Note that there are no guarantees on the ordering of values inside the
349
     * array, and it may change when more values are added or removed.
350
     *
351
     * Requirements:
352
     *
353
     * - `index` must be strictly less than {length}.
354
     */
355
    function at(UintSet storage set, uint256 index) internal view returns (uint256) {
356
        return uint256(_at(set._inner, index));
357
    }
358

                            
                        
359
    /**
360
     * @dev Return the entire set in an array
361
     *
362
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
363
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
364
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
365
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
366
     */
367
    function values(UintSet storage set) internal view returns (uint256[] memory) {
368
        bytes32[] memory store = _values(set._inner);
369
        uint256[] memory result;
370

                            
                        
371
        /// @solidity memory-safe-assembly
372
        assembly {
373
            result := store
374
        }
375

                            
                        
376
        return result;
377
    }
378
}
379

                            
                        

Lines covered: 0 / 0 (0.0%)

1
// SPDX-License-Identifier: MIT
2
pragma solidity 0.8.17;
3

                            
                        
4
import "./IERC20.sol";
5

                            
                        
6
/**
7
 * Based on the stETH:
8
 *  -   https://docs.lido.fi/contracts/lido#
9
 */
10
interface ICollateralToken is IERC20 {
11
    // Returns the amount of shares that corresponds to _ethAmount protocol-controlled Ether
12
    function getSharesByPooledEth(uint256 _ethAmount) external view returns (uint256);
13

                            
                        
14
    // Returns the amount of Ether that corresponds to _sharesAmount token shares
15
    function getPooledEthByShares(uint256 _sharesAmount) external view returns (uint256);
16

                            
                        
17
    // Moves `_sharesAmount` token shares from the caller's account to the `_recipient` account.
18
    function transferShares(address _recipient, uint256 _sharesAmount) external returns (uint256);
19

                            
                        
20
    // Returns the amount of shares owned by _account
21
    function sharesOf(address _account) external view returns (uint256);
22

                            
                        
23
    // Returns authorized oracle address
24
    function getOracle() external view returns (address);
25
}
26

                            
                        

Lines covered: 0 / 0 (0.0%)

1
// SPDX-License-Identifier: MIT
2
pragma solidity 0.8.17;
3

                            
                        
4
/**
5
 * Based on the stETH:
6
 *  -   https://docs.lido.fi/contracts/lido#
7
 */
8
interface ICollateralTokenOracle {
9
    // Return beacon specification data.
10
    function getBeaconSpec()
11
        external
12
        view
13
        returns (
14
            uint64 epochsPerFrame,
15
            uint64 slotsPerEpoch,
16
            uint64 secondsPerSlot,
17
            uint64 genesisTime
18
        );
19
}
20

                            
                        

Lines covered: 0 / 0 (0.0%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
/**
6
 * Based on the OpenZeppelin IER20 interface:
7
 * https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol
8
 *
9
 * @dev Interface of the ERC20 standard as defined in the EIP.
10
 */
11
interface IERC20 {
12
    /**
13
     * @dev Returns the amount of tokens in existence.
14
     */
15
    function totalSupply() external view returns (uint256);
16

                            
                        
17
    /**
18
     * @dev Returns the amount of tokens owned by `account`.
19
     */
20
    function balanceOf(address account) external view returns (uint256);
21

                            
                        
22
    /**
23
     * @dev Moves `amount` tokens from the caller's account to `recipient`.
24
     *
25
     * Returns a boolean value indicating whether the operation succeeded.
26
     *
27
     * Emits a {Transfer} event.
28
     */
29
    function transfer(address recipient, uint256 amount) external returns (bool);
30

                            
                        
31
    /**
32
     * @dev Returns the remaining number of tokens that `spender` will be
33
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
34
     * zero by default.
35
     *
36
     * This value changes when {approve} or {transferFrom} are called.
37
     */
38
    function allowance(address owner, address spender) external view returns (uint256);
39

                            
                        
40
    function increaseAllowance(address spender, uint256 addedValue) external returns (bool);
41

                            
                        
42
    function decreaseAllowance(address spender, uint256 subtractedValue) external returns (bool);
43

                            
                        
44
    /**
45
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
46
     *
47
     * Returns a boolean value indicating whether the operation succeeded.
48
     *
49
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
50
     * that someone may use both the old and the new allowance by unfortunate
51
     * transaction ordering. One possible solution to mitigate this race
52
     * condition is to first reduce the spender's allowance to 0 and set the
53
     * desired value afterwards:
54
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
55
     *
56
     * Emits an {Approval} event.
57
     */
58
    function approve(address spender, uint256 amount) external returns (bool);
59

                            
                        
60
    /**
61
     * @dev Moves `amount` tokens from `sender` to `recipient` using the
62
     * allowance mechanism. `amount` is then deducted from the caller's
63
     * allowance.
64
     *
65
     * Returns a boolean value indicating whether the operation succeeded.
66
     *
67
     * Emits a {Transfer} event.
68
     */
69
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
70

                            
                        
71
    function name() external view returns (string memory);
72

                            
                        
73
    function symbol() external view returns (string memory);
74

                            
                        
75
    function decimals() external view returns (uint8);
76

                            
                        
77
    /**
78
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
79
     * another (`to`).
80
     *
81
     * Note that `value` may be zero.
82
     */
83
    event Transfer(address indexed from, address indexed to, uint256 value);
84

                            
                        
85
    /**
86
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
87
     * a call to {approve}. `value` is the new allowance.
88
     */
89
    event Approval(address indexed owner, address indexed spender, uint256 value);
90
}
91

                            
                        

Lines covered: 0 / 0 (0.0%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
/**
6
 * @dev Interface of the ERC2612 standard as defined in the EIP.
7
 *
8
 * Adds the {permit} method, which can be used to change one's
9
 * {IERC20-allowance} without having to send a transaction, by signing a
10
 * message. This allows users to spend tokens without having to hold Ether.
11
 *
12
 * See https://eips.ethereum.org/EIPS/eip-2612.
13
 *
14
 * Code adapted from https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2237/
15
 */
16
interface IERC2612 {
17
    /**
18
     * @dev Sets `amount` as the allowance of `spender` over `owner`'s tokens,
19
     * given `owner`'s signed approval.
20
     *
21
     * IMPORTANT: The same issues {IERC20-approve} has related to transaction
22
     * ordering also apply here.
23
     *
24
     * Emits an {Approval} event.
25
     *
26
     * Requirements:
27
     *
28
     * - `owner` cannot be the zero address.
29
     * - `spender` cannot be the zero address.
30
     * - `deadline` must be a timestamp in the future.
31
     * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
32
     * over the EIP712-formatted function arguments.
33
     * - the signature must use ``owner``'s current nonce (see {nonces}).
34
     *
35
     * For more information on the signature format, see the
36
     * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
37
     * section].
38
     */
39
    function permit(
40
        address owner,
41
        address spender,
42
        uint256 amount,
43
        uint256 deadline,
44
        uint8 v,
45
        bytes32 r,
46
        bytes32 s
47
    ) external;
48

                            
                        
49
    /**
50
     * @dev Returns the current ERC2612 nonce for `owner`. This value must be
51
     * included whenever a signature is generated for {permit}.
52
     *
53
     * Every successful call to {permit} increases `owner`'s nonce by one. This
54
     * prevents a signature from being used multiple times.
55
     *
56
     * `owner` can limit the time a Permit is valid for by setting `deadline` to
57
     * a value in the near future. The deadline argument can be set to uint256(-1) to
58
     * create Permits that effectively never expire.
59
     */
60
    function nonces(address owner) external view returns (uint256);
61

                            
                        
62
    function version() external view returns (string memory);
63

                            
                        
64
    function permitTypeHash() external view returns (bytes32);
65

                            
                        
66
    function domainSeparator() external view returns (bytes32);
67
}
68

                            
                        

Lines covered: 0 / 0 (0.0%)

1
// SPDX-License-Identifier: AGPL-3.0-only
2
pragma solidity 0.8.17;
3

                            
                        
4
import "./EnumerableSet.sol";
5

                            
                        
6
/// @notice Role based Authority that supports up to 256 roles.
7
/// @author BadgerDAO
8
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/authorities/RolesAuthority.sol)
9
/// @author Modified from Dappsys (https://github.com/dapphub/ds-roles/blob/master/src/roles.sol)
10
interface IRolesAuthority {
11
    event UserRoleUpdated(address indexed user, uint8 indexed role, bool enabled);
12

                            
                        
13
    event PublicCapabilityUpdated(address indexed target, bytes4 indexed functionSig, bool enabled);
14
    event CapabilityBurned(address indexed target, bytes4 indexed functionSig);
15

                            
                        
16
    event RoleCapabilityUpdated(
17
        uint8 indexed role,
18
        address indexed target,
19
        bytes4 indexed functionSig,
20
        bool enabled
21
    );
22

                            
                        
23
    enum CapabilityFlag {
24
        None,
25
        Public,
26
        Burned
27
    }
28
}
29

                            
                        

Lines covered: 4 / 11 (36.4%)

1
// SPDX-License-Identifier: MIT
2
// OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol)
3

                            
                        
4
pragma solidity 0.8.17;
5

                            
                        
6
import "./Context.sol";
7

                            
                        
8
/**
9
 * @dev Contract module which provides a basic access control mechanism, where
10
 * there is an account (an owner) that can be granted exclusive access to
11
 * specific functions.
12
 *
13
 * By default, the owner account will be the one that deploys the contract. This
14
 * can later be changed with {transferOwnership}.
15
 *
16
 * This module is used through inheritance. It will make available the modifier
17
 * `onlyOwner`, which can be applied to your functions to restrict their use to
18
 * the owner.
19
 */
20
abstract contract Ownable is Context {
21
    address private _owner;
22

                            
                        
23
    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
24

                            
                        
25
    /**
26
     * @dev Initializes the contract setting the deployer as the initial owner.
27
     */
28
    constructor() {
29
        _transferOwnership(_msgSender());
30
    }
31

                            
                        
32
    /**
33
     * @dev Throws if called by any account other than the owner.
34
     */
35
    modifier onlyOwner() {
36
        _checkOwner();
37
        _;
38
    }
39

                            
                        
40
    /**
41
     * @dev Returns the address of the current owner.
42
     */
43
    function owner() public view virtual returns (address) {
44
        return _owner;
45
    }
46

                            
                        
47
    /**
48
     * @dev Throws if the sender is not the owner.
49
     */
50
    function _checkOwner() internal view virtual {
51
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
52
    }
53

                            
                        
54
    /**
55
     * @dev Leaves the contract without owner. It will not be possible to call
56
     * `onlyOwner` functions anymore. Can only be called by the current owner.
57
     *
58
     * NOTE: Renouncing ownership will leave the contract without an owner,
59
     * thereby removing any functionality that is only available to the owner.
60
     */
61
    function renounceOwnership() public virtual onlyOwner {
62
        _transferOwnership(address(0));
63
    }
64

                            
                        
65
    /**
66
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
67
     * Can only be called by the current owner.
68
     */
69
    function transferOwnership(address newOwner) public virtual onlyOwner {
70
        require(newOwner != address(0), "Ownable: new owner is the zero address");
71
        _transferOwnership(newOwner);
72
    }
73

                            
                        
74
    /**
75
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
76
     * Internal function without access restriction.
77
     */
78
    function _transferOwnership(address newOwner) internal virtual {
79
        address oldOwner = _owner;
80
        _owner = newOwner;
81
        emit OwnershipTransferred(oldOwner, newOwner);
82
    }
83
}
84

                            
                        

Lines covered: 0 / 5 (0.0%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4
import "../Interfaces/IPermitNonce.sol";
5

                            
                        
6
/**
7
 * @dev This abstract contract provides a mapping from address to nonce (uint256) used for permit signature
8
 */
9
contract PermitNonce is IPermitNonce {
10
    mapping(address => uint256) internal _nonces;
11

                            
                        
12
    /// @dev Increase current nonce for msg.sender by one.
13
    /// @notice This function could be used to invalidate any signed permit out there
14
    function increasePermitNonce() external returns (uint256) {
15
        return ++_nonces[msg.sender];
16
    }
17

                            
                        
18
    /// @dev Return current nonce for msg.sender fOR EIP-2612 compatibility
19
    function nonces(address owner) external view virtual returns (uint256) {
20
        return _nonces[owner];
21
    }
22
}
23

                            
                        

Lines covered: 7 / 8 (87.5%)

1
// SPDX-License-Identifier: MIT
2
// OpenZeppelin Contracts (last updated v4.6.0) (proxy/Proxy.sol)
3

                            
                        
4
pragma solidity 0.8.17;
5

                            
                        
6
/**
7
 * @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM
8
 * instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to
9
 * be specified by overriding the virtual {_implementation} function.
10
 *
11
 * Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a
12
 * different contract through the {_delegate} function.
13
 *
14
 * The success and return data of the delegated call will be returned back to the caller of the proxy.
15
 * @dev BadgerDAO: Simplified to the core delegation functionality, without any additional features.
16
 */
17
contract Proxy {
18
    /**
19
     * @dev Delegates the current call to `implementation`.
20
     *
21
     * This function does not return to its internal call site, it will return directly to the external caller.
22
     */
23
    function _delegate(address implementation) internal virtual {
24
        assembly {
25
            // Copy msg.data. We take full control of memory in this inline assembly
26
            // block because it will not return to Solidity code. We overwrite the
27
            // Solidity scratch pad at memory position 0.
28
            calldatacopy(0, 0, calldatasize())
29

                            
                        
30
            // Call the implementation.
31
            // out and outsize are 0 because we don't know the size yet.
32
            let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
33

                            
                        
34
            // Copy the returned data.
35
            returndatacopy(0, 0, returndatasize())
36

                            
                        
37
            switch result
38
            // delegatecall returns 0 on error.
39
            case 0 {
40
                revert(0, returndatasize())
41
            }
42
            default {
43
                return(0, returndatasize())
44
            }
45
        }
46
    }
47
}
48

                            
                        

Lines covered: 3 / 6 (50.0%)

1
// SPDX-License-Identifier: AGPL-3.0-only
2
pragma solidity 0.8.17;
3

                            
                        
4
/// @notice Gas optimized reentrancy protection for smart contracts.
5
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/ReentrancyGuard.sol)
6
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/security/ReentrancyGuard.sol)
7
abstract contract ReentrancyGuard {
8
    uint256 internal constant OPEN = 1;
9
    uint256 internal constant LOCKED = 2;
10

                            
                        
11
    uint256 public locked = OPEN;
12

                            
                        
13
    modifier nonReentrant() virtual {
14
        require(locked == OPEN, "ReentrancyGuard: Reentrancy in nonReentrant call");
15

                            
                        
16
        locked = LOCKED;
17

                            
                        
18
        _;
19

                            
                        
20
        locked = OPEN;
21
    }
22
}
23

                            
                        

Lines covered: 17 / 43 (39.5%)

1
// SPDX-License-Identifier: AGPL-3.0-only
2
pragma solidity 0.8.17;
3

                            
                        
4
import {IRolesAuthority} from "./IRolesAuthority.sol";
5
import {Auth, Authority} from "./Auth.sol";
6
import "./EnumerableSet.sol";
7

                            
                        
8
/// @notice Role based Authority that supports up to 256 roles.
9
/// @author BadgerDAO
10
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/authorities/RolesAuthority.sol)
11
/// @author Modified from Dappsys (https://github.com/dapphub/ds-roles/blob/master/src/roles.sol)
12
contract RolesAuthority is IRolesAuthority, Auth, Authority {
13
    using EnumerableSet for EnumerableSet.Bytes32Set;
14
    using EnumerableSet for EnumerableSet.AddressSet;
15

                            
                        
16
    /*//////////////////////////////////////////////////////////////
17
                               CONSTRUCTOR
18
    //////////////////////////////////////////////////////////////*/
19

                            
                        
20
    constructor(address _owner, Authority _authority) Auth(_owner, _authority) {}
21

                            
                        
22
    /*//////////////////////////////////////////////////////////////
23
                            ROLE/USER STORAGE
24
    //////////////////////////////////////////////////////////////*/
25

                            
                        
26
    EnumerableSet.AddressSet internal users;
27
    EnumerableSet.AddressSet internal targets;
28
    mapping(address => EnumerableSet.Bytes32Set) internal enabledFunctionSigsByTarget;
29

                            
                        
30
    EnumerableSet.Bytes32Set internal enabledFunctionSigsPublic;
31

                            
                        
32
    mapping(address => bytes32) public getUserRoles;
33

                            
                        
34
    mapping(address => mapping(bytes4 => CapabilityFlag)) public capabilityFlag;
35

                            
                        
36
    mapping(address => mapping(bytes4 => bytes32)) public getRolesWithCapability;
37

                            
                        
38
    function doesUserHaveRole(address user, uint8 role) public view virtual returns (bool) {
39
        return (uint256(getUserRoles[user]) >> role) & 1 != 0;
40
    }
41

                            
                        
42
    function doesRoleHaveCapability(
43
        uint8 role,
44
        address target,
45
        bytes4 functionSig
46
    ) public view virtual returns (bool) {
47
        return (uint256(getRolesWithCapability[target][functionSig]) >> role) & 1 != 0;
48
    }
49

                            
                        
50
    function isPublicCapability(address target, bytes4 functionSig) public view returns (bool) {
51
        return capabilityFlag[target][functionSig] == CapabilityFlag.Public;
52
    }
53

                            
                        
54
    /*//////////////////////////////////////////////////////////////
55
                           AUTHORIZATION LOGIC
56
    //////////////////////////////////////////////////////////////*/
57

                            
                        
58
    /**
59
        @notice A user can call a given function signature on a given target address if:
60
            - The capability has not been burned
61
            - That capability is public, or the user has a role that has been granted the capability to call the function
62
     */
63
    function canCall(
64
        address user,
65
        address target,
66
        bytes4 functionSig
67
    ) public view virtual override returns (bool) {
68
        CapabilityFlag flag = capabilityFlag[target][functionSig];
69

                            
                        
70
        if (flag == CapabilityFlag.Burned) {
71
            return false;
72
        } else if (flag == CapabilityFlag.Public) {
73
            return true;
74
        } else {
75
            return bytes32(0) != getUserRoles[user] & getRolesWithCapability[target][functionSig];
76
        }
77
    }
78

                            
                        
79
    /*//////////////////////////////////////////////////////////////
80
                   ROLE CAPABILITY CONFIGURATION LOGIC
81
    //////////////////////////////////////////////////////////////*/
82

                            
                        
83
    /// @notice Set a capability flag as public, meaning any account can call it. Or revoke this capability.
84
    /// @dev A capability cannot be made public if it has been burned.
85
    function setPublicCapability(
86
        address target,
87
        bytes4 functionSig,
88
        bool enabled
89
    ) public virtual requiresAuth {
90
        require(
91
            capabilityFlag[target][functionSig] != CapabilityFlag.Burned,
92
            "RolesAuthority: Capability Burned"
93
        );
94

                            
                        
95
        if (enabled) {
96
            capabilityFlag[target][functionSig] = CapabilityFlag.Public;
97
        } else {
98
            capabilityFlag[target][functionSig] = CapabilityFlag.None;
99
        }
100

                            
                        
101
        emit PublicCapabilityUpdated(target, functionSig, enabled);
102
    }
103

                            
                        
104
    /// @notice Grant a specified role the ability to call a function on a target.
105
    /// @notice Has no effect
106
    function setRoleCapability(
107
        uint8 role,
108
        address target,
109
        bytes4 functionSig,
110
        bool enabled
111
    ) public virtual requiresAuth {
112
        if (enabled) {
113
            getRolesWithCapability[target][functionSig] |= bytes32(1 << role);
114
            enabledFunctionSigsByTarget[target].add(bytes32(functionSig));
115

                            
                        
116
            if (!targets.contains(target)) {
117
                targets.add(target);
118
            }
119
        } else {
120
            getRolesWithCapability[target][functionSig] &= ~bytes32(1 << role);
121
            enabledFunctionSigsByTarget[target].remove(bytes32(functionSig));
122

                            
                        
123
            // If no enabled function signatures exist for this target, remove target
124
            if (enabledFunctionSigsByTarget[target].length() == 0) {
125
                targets.remove(target);
126
            }
127
        }
128

                            
                        
129
        emit RoleCapabilityUpdated(role, target, functionSig, enabled);
130
    }
131

                            
                        
132
    /// @notice Permanently burns a capability for a target.
133
    function burnCapability(address target, bytes4 functionSig) public virtual requiresAuth {
134
        require(
135
            capabilityFlag[target][functionSig] != CapabilityFlag.Burned,
136
            "RolesAuthority: Capability Burned"
137
        );
138
        capabilityFlag[target][functionSig] = CapabilityFlag.Burned;
139

                            
                        
140
        emit CapabilityBurned(target, functionSig);
141
    }
142

                            
                        
143
    /*//////////////////////////////////////////////////////////////
144
                       USER ROLE ASSIGNMENT LOGIC
145
    //////////////////////////////////////////////////////////////*/
146

                            
                        
147
    function setUserRole(address user, uint8 role, bool enabled) public virtual requiresAuth {
148
        if (enabled) {
149
            getUserRoles[user] |= bytes32(1 << role);
150

                            
                        
151
            if (!users.contains(user)) {
152
                users.add(user);
153
            }
154
        } else {
155
            getUserRoles[user] &= ~bytes32(1 << role);
156

                            
                        
157
            // Remove user if no more roles
158
            if (getUserRoles[user] == bytes32(0)) {
159
                users.remove(user);
160
            }
161
        }
162

                            
                        
163
        emit UserRoleUpdated(user, role, enabled);
164
    }
165
}
166

                            
                        

Lines covered: 0 / 5 (0.0%)

1
// SPDX-License-Identifier: MIT
2
// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/utils/SafeERC20.sol)
3

                            
                        
4
pragma solidity 0.8.17;
5

                            
                        
6
import "./IERC20.sol";
7
import "./Address.sol";
8

                            
                        
9
/**
10
 * @title SafeERC20
11
 * @dev Wrappers around ERC20 operations that throw on failure (when the token
12
 * contract returns false). Tokens that return no value (and instead revert or
13
 * throw on failure) are also supported, non-reverting calls are assumed to be
14
 * successful.
15
 * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
16
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
17
 */
18
library SafeERC20 {
19
    using Address for address;
20

                            
                        
21
    /**
22
     * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
23
     * non-reverting calls are assumed to be successful.
24
     */
25
    function safeTransfer(IERC20 token, address to, uint256 value) internal {
26
        _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
27
    }
28

                            
                        
29
    /// @dev Calls approve while checking bool return value, handles no-return tokens
30
    function safeApprove(IERC20 token, address spender, uint256 amount) internal {
31
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, amount));
32
    }
33

                            
                        
34
    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
35
        _callOptionalReturn(
36
            token,
37
            abi.encodeWithSelector(token.transferFrom.selector, from, to, value)
38
        );
39
    }
40

                            
                        
41
    /**
42
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
43
     * on the return value: the return value is optional (but if data is returned, it must not be false).
44
     * @param token The token targeted by the call.
45
     * @param data The call data (encoded using abi.encode or one of its variants).
46
     */
47
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
48
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
49
        // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
50
        // the target address contains contract code and also asserts for success in the low-level call.
51

                            
                        
52
        bytes memory returndata = address(token).functionCall(
53
            data,
54
            "SafeERC20: low-level call failed"
55
        );
56
        require(
57
            returndata.length == 0 || abi.decode(returndata, (bool)),
58
            "SafeERC20: ERC20 operation did not succeed"
59
        );
60
    }
61
}
62

                            
                        

Lines covered: 0 / 1 (0.0%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
/**
6
 * Based on OpenZeppelin's SafeMath:
7
 * https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/math/SafeMath.sol
8
 *
9
 * @dev Wrappers over Solidity's arithmetic operations with added overflow
10
 * checks.
11
 *
12
 * Arithmetic operations in Solidity wrap on overflow. This can easily result
13
 * in bugs, because programmers usually assume that an overflow raises an
14
 * error, which is the standard behavior in high level programming languages.
15
 * `SafeMath` restores this intuition by reverting the transaction when an
16
 * operation overflows.
17
 *
18
 * Using this library instead of the unchecked operations eliminates an entire
19
 * class of bugs, so it's recommended to use it always.
20
 */
21
library SafeMath {
22
    /**
23
     * @dev Returns the addition of two unsigned integers, reverting on
24
     * overflow.
25
     *
26
     * Counterpart to Solidity's `+` operator.
27
     *
28
     * Requirements:
29
     * - Addition cannot overflow.
30
     */
31
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
32
        uint256 c = a + b;
33
        require(c >= a, "SafeMath: addition overflow");
34

                            
                        
35
        return c;
36
    }
37

                            
                        
38
    /**
39
     * @dev Returns the subtraction of two unsigned integers, reverting on
40
     * overflow (when the result is negative).
41
     *
42
     * Counterpart to Solidity's `-` operator.
43
     *
44
     * Requirements:
45
     * - Subtraction cannot overflow.
46
     */
47
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
48
        return sub(a, b, "SafeMath: subtraction overflow");
49
    }
50

                            
                        
51
    /**
52
     * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
53
     * overflow (when the result is negative).
54
     *
55
     * Counterpart to Solidity's `-` operator.
56
     *
57
     * Requirements:
58
     * - Subtraction cannot overflow.
59
     *
60
     * _Available since v2.4.0._
61
     */
62
    function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
63
        require(b <= a, errorMessage);
64
        uint256 c = a - b;
65

                            
                        
66
        return c;
67
    }
68

                            
                        
69
    /**
70
     * @dev Returns the multiplication of two unsigned integers, reverting on
71
     * overflow.
72
     *
73
     * Counterpart to Solidity's `*` operator.
74
     *
75
     * Requirements:
76
     * - Multiplication cannot overflow.
77
     */
78
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
79
        // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
80
        // benefit is lost if 'b' is also tested.
81
        // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
82
        if (a == 0) {
83
            return 0;
84
        }
85

                            
                        
86
        uint256 c = a * b;
87
        require(c / a == b, "SafeMath: multiplication overflow");
88

                            
                        
89
        return c;
90
    }
91

                            
                        
92
    /**
93
     * @dev Returns the integer division of two unsigned integers. Reverts on
94
     * division by zero. The result is rounded towards zero.
95
     *
96
     * Counterpart to Solidity's `/` operator. Note: this function uses a
97
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
98
     * uses an invalid opcode to revert (consuming all remaining gas).
99
     *
100
     * Requirements:
101
     * - The divisor cannot be zero.
102
     */
103
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
104
        return div(a, b, "SafeMath: division by zero");
105
    }
106

                            
                        
107
    /**
108
     * @dev Returns the integer division of two unsigned integers. Reverts with custom message on
109
     * division by zero. The result is rounded towards zero.
110
     *
111
     * Counterpart to Solidity's `/` operator. Note: this function uses a
112
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
113
     * uses an invalid opcode to revert (consuming all remaining gas).
114
     *
115
     * Requirements:
116
     * - The divisor cannot be zero.
117
     *
118
     * _Available since v2.4.0._
119
     */
120
    function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
121
        // Solidity only automatically asserts when dividing by 0
122
        require(b > 0, errorMessage);
123
        uint256 c = a / b;
124
        // assert(a == b * c + a % b); // There is no case in which this doesn't hold
125

                            
                        
126
        return c;
127
    }
128

                            
                        
129
    /**
130
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
131
     * Reverts when dividing by zero.
132
     *
133
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
134
     * opcode (which leaves remaining gas untouched) while Solidity uses an
135
     * invalid opcode to revert (consuming all remaining gas).
136
     *
137
     * Requirements:
138
     * - The divisor cannot be zero.
139
     */
140
    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
141
        return mod(a, b, "SafeMath: modulo by zero");
142
    }
143

                            
                        
144
    /**
145
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
146
     * Reverts with custom message when dividing by zero.
147
     *
148
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
149
     * opcode (which leaves remaining gas untouched) while Solidity uses an
150
     * invalid opcode to revert (consuming all remaining gas).
151
     *
152
     * Requirements:
153
     * - The divisor cannot be zero.
154
     *
155
     * _Available since v2.4.0._
156
     */
157
    function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
158
        require(b != 0, errorMessage);
159
        return a % b;
160
    }
161
}
162

                            
                        

Lines covered: 31 / 43 (72.1%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
import "./Dependencies/Create3.sol";
6
import "./Dependencies/Ownable.sol";
7

                            
                        
8
contract EBTCDeployer is Ownable {
9
    string public constant name = "eBTC Deployer";
10

                            
                        
11
    string public constant AUTHORITY = "ebtc.v1.authority";
12
    string public constant LIQUIDATION_LIBRARY = "ebtc.v1.liquidationLibrary";
13
    string public constant CDP_MANAGER = "ebtc.v1.cdpManager";
14
    string public constant BORROWER_OPERATIONS = "ebtc.v1.borrowerOperations";
15

                            
                        
16
    string public constant PRICE_FEED = "ebtc.v1.priceFeed";
17
    string public constant SORTED_CDPS = "ebtc.v1.sortedCdps";
18

                            
                        
19
    string public constant ACTIVE_POOL = "ebtc.v1.activePool";
20
    string public constant COLL_SURPLUS_POOL = "ebtc.v1.collSurplusPool";
21

                            
                        
22
    string public constant HINT_HELPERS = "ebtc.v1.hintHelpers";
23
    string public constant EBTC_TOKEN = "ebtc.v1.eBTCToken";
24
    string public constant FEE_RECIPIENT = "ebtc.v1.feeRecipient";
25
    string public constant MULTI_CDP_GETTER = "ebtc.v1.multiCdpGetter";
26

                            
                        
27
    event ContractDeployed(address indexed contractAddress, string contractName, bytes32 salt);
28

                            
                        
29
    struct EbtcAddresses {
30
        address authorityAddress;
31
        address liquidationLibraryAddress;
32
        address cdpManagerAddress;
33
        address borrowerOperationsAddress;
34
        address priceFeedAddress;
35
        address sortedCdpsAddress;
36
        address activePoolAddress;
37
        address collSurplusPoolAddress;
38
        address hintHelpersAddress;
39
        address ebtcTokenAddress;
40
        address feeRecipientAddress;
41
        address multiCdpGetterAddress;
42
    }
43

                            
                        
44
    /**
45
    @notice Helper method to return a set of future addresses for eBTC. Intended to be used in the order specified.
46
    
47
    @dev The order is as follows:
48
    0: authority
49
    1: liquidationLibrary
50
    2: cdpManager
51
    3: borrowerOperations
52
    4: priceFeed
53
    5; sortedCdps
54
    6: activePool
55
    7: collSurplusPool
56
    8: hintHelpers
57
    9: eBTCToken
58
    10: feeRecipient
59
    11: multiCdpGetter
60

                            
                        
61

                            
                        
62
     */
63
    function getFutureEbtcAddresses() public view returns (EbtcAddresses memory) {
64
        EbtcAddresses memory addresses = EbtcAddresses(
65
            Create3.addressOf(keccak256(abi.encodePacked(AUTHORITY))),
66
            Create3.addressOf(keccak256(abi.encodePacked(LIQUIDATION_LIBRARY))),
67
            Create3.addressOf(keccak256(abi.encodePacked(CDP_MANAGER))),
68
            Create3.addressOf(keccak256(abi.encodePacked(BORROWER_OPERATIONS))),
69
            Create3.addressOf(keccak256(abi.encodePacked(PRICE_FEED))),
70
            Create3.addressOf(keccak256(abi.encodePacked(SORTED_CDPS))),
71
            Create3.addressOf(keccak256(abi.encodePacked(ACTIVE_POOL))),
72
            Create3.addressOf(keccak256(abi.encodePacked(COLL_SURPLUS_POOL))),
73
            Create3.addressOf(keccak256(abi.encodePacked(HINT_HELPERS))),
74
            Create3.addressOf(keccak256(abi.encodePacked(EBTC_TOKEN))),
75
            Create3.addressOf(keccak256(abi.encodePacked(FEE_RECIPIENT))),
76
            Create3.addressOf(keccak256(abi.encodePacked(MULTI_CDP_GETTER)))
77
        );
78

                            
                        
79
        return addresses;
80
    }
81

                            
                        
82
    /**
83
        @notice Deploy a contract using salt in string format and arbitrary runtime code.
84
        @dev Intended use is: get the future eBTC addresses, then deploy the appropriate contract to each address via this method, building the constructor using the mapped addresses
85
        @dev no enforcment of bytecode at address as we can't know the runtime code in this contract due to space constraints
86
        @dev gated to given deployer EOA to ensure no interference with process, given proper actions by deployer
87
     */
88
    function deploy(
89
        string memory _saltString,
90
        bytes memory _creationCode
91
    ) public returns (address deployedAddress) {
92
        bytes32 _salt = keccak256(abi.encodePacked(_saltString));
93
        deployedAddress = Create3.create3(_salt, _creationCode);
94
        emit ContractDeployed(deployedAddress, _saltString, _salt);
95
    }
96

                            
                        
97
    function deployWithCreationCodeAndConstructorArgs(
98
        string memory _saltString,
99
        bytes memory creationCode,
100
        bytes memory constructionArgs
101
    ) external returns (address) {
102
        bytes memory _data = abi.encodePacked(creationCode, constructionArgs);
103
        return deploy(_saltString, _data);
104
    }
105

                            
                        
106
    function deployWithCreationCode(
107
        string memory _saltString,
108
        bytes memory creationCode
109
    ) external returns (address) {
110
        return deploy(_saltString, creationCode);
111
    }
112

                            
                        
113
    function addressOf(string memory _saltString) external view returns (address) {
114
        bytes32 _salt = keccak256(abi.encodePacked(_saltString));
115
        return Create3.addressOf(_salt);
116
    }
117

                            
                        
118
    function addressOfSalt(bytes32 _salt) external view returns (address) {
119
        return Create3.addressOf(_salt);
120
    }
121

                            
                        
122
    /**
123
        @notice Create the creation code for a contract with the given runtime code.
124
        @dev credit: https://github.com/0xsequence/create3/blob/master/contracts/test_utils/Create3Imp.sol
125
     */
126
    function creationCodeFor(bytes memory _code) internal pure returns (bytes memory) {
127
        /*
128
      0x00    0x63         0x63XXXXXX  PUSH4 _code.length  size
129
      0x01    0x80         0x80        DUP1                size size
130
      0x02    0x60         0x600e      PUSH1 14            14 size size
131
      0x03    0x60         0x6000      PUSH1 00            0 14 size size
132
      0x04    0x39         0x39        CODECOPY            size
133
      0x05    0x60         0x6000      PUSH1 00            0 size
134
      0x06    0xf3         0xf3        RETURN
135
      <CODE>
136
    */
137

                            
                        
138
        return
139
            abi.encodePacked(hex"63", uint32(_code.length), hex"80_60_0E_60_00_39_60_00_F3", _code);
140
    }
141
}
142

                            
                        

Lines covered: 41 / 104 (39.4%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
import "./Interfaces/IEBTCToken.sol";
6

                            
                        
7
import "./Dependencies/AuthNoOwner.sol";
8
import "./Dependencies/PermitNonce.sol";
9

                            
                        
10
/*
11
 *
12
 * Based upon OpenZeppelin's ERC20 contract:
13
 * https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol
14
 *
15
 * and their EIP2612 (ERC20Permit / ERC712) functionality:
16
 * https://github.com/OpenZeppelin/openzeppelin-contracts/blob/53516bc555a454862470e7860a9b5254db4d00f5/contracts/token/ERC20/ERC20Permit.sol
17
 *
18
 *
19
 * --- Functionality added specific to the EBTCToken ---
20
 *
21
 * 1) Transfer protection: blacklist of addresses that are invalid recipients (i.e. core Liquity contracts) in external
22
 * transfer() and transferFrom() calls. The purpose is to protect users from losing tokens by mistakenly sending EBTC directly to a Liquity
23
 * core contract, when they should rather call the right function.
24
 */
25

                            
                        
26
contract EBTCToken is IEBTCToken, AuthNoOwner, PermitNonce {
27
    uint256 private _totalSupply;
28
    string internal constant _NAME = "EBTC Stablecoin";
29
    string internal constant _SYMBOL = "EBTC";
30
    string internal constant _VERSION = "1";
31
    uint8 internal constant _DECIMALS = 18;
32

                            
                        
33
    // --- Data for EIP2612 ---
34

                            
                        
35
    // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
36
    bytes32 private constant _PERMIT_TYPEHASH =
37
        0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
38
    // keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
39
    bytes32 private constant _TYPE_HASH =
40
        0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f;
41

                            
                        
42
    // Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to
43
    // invalidate the cached domain separator if the chain id changes.
44
    bytes32 private immutable _CACHED_DOMAIN_SEPARATOR;
45
    uint256 private immutable _CACHED_CHAIN_ID;
46

                            
                        
47
    bytes32 private immutable _HASHED_NAME;
48
    bytes32 private immutable _HASHED_VERSION;
49

                            
                        
50
    // User data for EBTC token
51
    mapping(address => uint256) private _balances;
52
    mapping(address => mapping(address => uint256)) private _allowances;
53

                            
                        
54
    // --- Addresses ---
55
    address public immutable cdpManagerAddress;
56
    address public immutable borrowerOperationsAddress;
57

                            
                        
58
    constructor(
59
        address _cdpManagerAddress,
60
        address _borrowerOperationsAddress,
61
        address _authorityAddress
62
    ) {
63
        _initializeAuthority(_authorityAddress);
64

                            
                        
65
        cdpManagerAddress = _cdpManagerAddress;
66
        borrowerOperationsAddress = _borrowerOperationsAddress;
67

                            
                        
68
        bytes32 hashedName = keccak256(bytes(_NAME));
69
        bytes32 hashedVersion = keccak256(bytes(_VERSION));
70

                            
                        
71
        _HASHED_NAME = hashedName;
72
        _HASHED_VERSION = hashedVersion;
73
        _CACHED_CHAIN_ID = _chainID();
74
        _CACHED_DOMAIN_SEPARATOR = _buildDomainSeparator(_TYPE_HASH, hashedName, hashedVersion);
75
    }
76

                            
                        
77
    // --- Functions for intra-Liquity calls ---
78

                            
                        
79
    /**
80
     * @notice Mint new tokens
81
     * @dev Internal system function - only callable by BorrowerOperations or CDPManager
82
     * @dev Governance can also expand the list of approved minters to enable other systems to mint tokens
83
     * @param _account The address to receive the newly minted tokens
84
     * @param _amount The amount of tokens to mint
85
     */
86
    function mint(address _account, uint256 _amount) external override {
87
        _requireCallerIsBOorCdpMOrAuth();
88
        _mint(_account, _amount);
89
    }
90

                            
                        
91
    /**
92
     * @notice Burn existing tokens
93
     * @dev Internal system function - only callable by BorrowerOperations or CDPManager
94
     * @dev Governance can also expand the list of approved burners to enable other systems to burn tokens
95
     * @param _account The address to burn tokens from
96
     * @param _amount The amount of tokens to burn
97
     */
98
    function burn(address _account, uint256 _amount) external override {
99
        _requireCallerIsBOorCdpMOrAuth();
100
        _burn(_account, _amount);
101
    }
102

                            
                        
103
    /**
104
     * @notice Burn existing tokens from caller
105
     * @dev Internal system function - only callable by BorrowerOperations or CDPManager
106
     * @dev Governance can also expand the list of approved burners to enable other systems to burn tokens
107
     * @param _amount The amount of tokens to burn
108
     */
109
    function burn(uint256 _amount) external {
110
        _requireCallerIsBOorCdpMOrAuth();
111
        _burn(msg.sender, _amount);
112
    }
113

                            
                        
114
    // --- External functions ---
115

                            
                        
116
    function totalSupply() external view override returns (uint256) {
117
        return _totalSupply;
118
    }
119

                            
                        
120
    function balanceOf(address account) external view override returns (uint256) {
121
        return _balances[account];
122
    }
123

                            
                        
124
    function transfer(address recipient, uint256 amount) external override returns (bool) {
125
        _requireValidRecipient(recipient);
126
        _transfer(msg.sender, recipient, amount);
127
        return true;
128
    }
129

                            
                        
130
    function allowance(address owner, address spender) external view override returns (uint256) {
131
        return _allowances[owner][spender];
132
    }
133

                            
                        
134
    function approve(address spender, uint256 amount) external override returns (bool) {
135
        _approve(msg.sender, spender, amount);
136
        return true;
137
    }
138

                            
                        
139
    function transferFrom(
140
        address sender,
141
        address recipient,
142
        uint256 amount
143
    ) external override returns (bool) {
144
        _requireValidRecipient(recipient);
145
        _transfer(sender, recipient, amount);
146

                            
                        
147
        uint256 cachedAllowance = _allowances[sender][msg.sender];
148
        if (cachedAllowance != type(uint256).max) {
149
            require(cachedAllowance >= amount, "ERC20: transfer amount exceeds allowance");
150
            unchecked {
151
                _approve(sender, msg.sender, cachedAllowance - amount);
152
            }
153
        }
154
        return true;
155
    }
156

                            
                        
157
    function increaseAllowance(
158
        address spender,
159
        uint256 addedValue
160
    ) external override returns (bool) {
161
        _approve(msg.sender, spender, _allowances[msg.sender][spender] + addedValue);
162
        return true;
163
    }
164

                            
                        
165
    function decreaseAllowance(
166
        address spender,
167
        uint256 subtractedValue
168
    ) external override returns (bool) {
169
        uint256 cachedAllowances = _allowances[msg.sender][spender];
170
        require(cachedAllowances >= subtractedValue, "ERC20: decreased allowance below zero");
171
        unchecked {
172
            _approve(msg.sender, spender, cachedAllowances - subtractedValue);
173
        }
174
        return true;
175
    }
176

                            
                        
177
    // --- EIP 2612 Functionality (https://eips.ethereum.org/EIPS/eip-2612) ---
178

                            
                        
179
    function DOMAIN_SEPARATOR() external view returns (bytes32) {
180
        return domainSeparator();
181
    }
182

                            
                        
183
    function domainSeparator() public view override returns (bytes32) {
184
        if (_chainID() == _CACHED_CHAIN_ID) {
185
            return _CACHED_DOMAIN_SEPARATOR;
186
        } else {
187
            return _buildDomainSeparator(_TYPE_HASH, _HASHED_NAME, _HASHED_VERSION);
188
        }
189
    }
190

                            
                        
191
    function permit(
192
        address owner,
193
        address spender,
194
        uint256 amount,
195
        uint256 deadline,
196
        uint8 v,
197
        bytes32 r,
198
        bytes32 s
199
    ) external override {
200
        require(deadline >= block.timestamp, "EBTC: expired deadline");
201
        bytes32 digest = keccak256(
202
            abi.encodePacked(
203
                "\x19\x01",
204
                domainSeparator(),
205
                keccak256(
206
                    abi.encode(_PERMIT_TYPEHASH, owner, spender, amount, _nonces[owner]++, deadline)
207
                )
208
            )
209
        );
210
        address recoveredAddress = ecrecover(digest, v, r, s);
211
        require(recoveredAddress == owner, "EBTC: invalid signature");
212
        _approve(owner, spender, amount);
213
    }
214

                            
                        
215
    /// @dev Return current nonce for msg.sender fOR EIP-2612 compatibility
216
    function nonces(address owner) external view override(IERC2612, PermitNonce) returns (uint256) {
217
        return _nonces[owner];
218
    }
219

                            
                        
220
    // --- Internal operations ---
221

                            
                        
222
    function _chainID() private view returns (uint256) {
223
        return block.chainid;
224
    }
225

                            
                        
226
    function _buildDomainSeparator(
227
        bytes32 typeHash,
228
        bytes32 name,
229
        bytes32 version
230
    ) private view returns (bytes32) {
231
        return keccak256(abi.encode(typeHash, name, version, _chainID(), address(this)));
232
    }
233

                            
                        
234
    // --- Internal operations ---
235
    // Warning: sanity checks (for sender and recipient) should have been done before calling these internal functions
236

                            
                        
237
    function _transfer(address sender, address recipient, uint256 amount) internal {
238
        require(sender != address(0), "EBTCToken: zero sender!");
239
        require(recipient != address(0), "EBTCToken: zero recipient!");
240

                            
                        
241
        uint256 cachedSenderBalances = _balances[sender];
242
        require(cachedSenderBalances >= amount, "ERC20: transfer amount exceeds balance");
243

                            
                        
244
        unchecked {
245
            // Safe because of the check above
246
            _balances[sender] = cachedSenderBalances - amount;
247
        }
248

                            
                        
249
        _balances[recipient] = _balances[recipient] + amount;
250
        emit Transfer(sender, recipient, amount);
251
    }
252

                            
                        
253
    function _mint(address account, uint256 amount) internal {
254
        require(account != address(0), "EBTCToken: mint to zero recipient!");
255

                            
                        
256
        _totalSupply = _totalSupply + amount;
257
        _balances[account] = _balances[account] + amount;
258
        emit Transfer(address(0), account, amount);
259
    }
260

                            
                        
261
    function _burn(address account, uint256 amount) internal {
262
        require(account != address(0), "EBTCToken: burn from zero account!");
263

                            
                        
264
        uint256 cachedBalance = _balances[account];
265
        require(cachedBalance >= amount, "ERC20: burn amount exceeds balance");
266

                            
                        
267
        unchecked {
268
            // Safe because of the check above
269
            _balances[account] = cachedBalance - amount;
270
        }
271

                            
                        
272
        _totalSupply = _totalSupply - amount;
273
        emit Transfer(account, address(0), amount);
274
    }
275

                            
                        
276
    function _approve(address owner, address spender, uint256 amount) internal {
277
        require(owner != address(0), "EBTCToken: zero approve owner!");
278
        require(spender != address(0), "EBTCToken: zero approve spender!");
279

                            
                        
280
        _allowances[owner][spender] = amount;
281
        emit Approval(owner, spender, amount);
282
    }
283

                            
                        
284
    // --- 'require' functions ---
285

                            
                        
286
    function _requireValidRecipient(address _recipient) internal view {
287
        require(
288
            _recipient != address(0) && _recipient != address(this),
289
            "EBTC: Cannot transfer tokens directly to the EBTC token contract or the zero address"
290
        );
291
        require(
292
            _recipient != cdpManagerAddress && _recipient != borrowerOperationsAddress,
293
            "EBTC: Cannot transfer tokens directly to the CdpManager or BorrowerOps"
294
        );
295
    }
296

                            
                        
297
    function _requireCallerIsBorrowerOperations() internal view {
298
        require(
299
            msg.sender == borrowerOperationsAddress,
300
            "EBTCToken: Caller is not BorrowerOperations"
301
        );
302
    }
303

                            
                        
304
    /// @dev authority check last to short-circuit in the case of use by usual immutable addresses
305
    function _requireCallerIsBOorCdpMOrAuth() internal view {
306
        require(
307
            msg.sender == borrowerOperationsAddress ||
308
                msg.sender == cdpManagerAddress ||
309
                isAuthorized(msg.sender, msg.sig),
310
            "EBTC: Caller is neither BorrowerOperations nor CdpManager nor authorized"
311
        );
312
    }
313

                            
                        
314
    function _requireCallerIsCdpM() internal view {
315
        require(msg.sender == cdpManagerAddress, "EBTC: Caller is not CdpManager");
316
    }
317

                            
                        
318
    // --- Optional functions ---
319

                            
                        
320
    function name() external pure override returns (string memory) {
321
        return _NAME;
322
    }
323

                            
                        
324
    function symbol() external pure override returns (string memory) {
325
        return _SYMBOL;
326
    }
327

                            
                        
328
    function decimals() external pure override returns (uint8) {
329
        return _DECIMALS;
330
    }
331

                            
                        
332
    function version() external pure override returns (string memory) {
333
        return _VERSION;
334
    }
335

                            
                        
336
    function permitTypeHash() external pure override returns (bytes32) {
337
        return _PERMIT_TYPEHASH;
338
    }
339
}
340

                            
                        

Lines covered: 2 / 8 (25.0%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
import "./Dependencies/Ownable.sol";
6
import "./Dependencies/AuthNoOwner.sol";
7
import "./Dependencies/IERC20.sol";
8
import "./Dependencies/SafeERC20.sol";
9

                            
                        
10
/**
11
    @notice Minimal fee recipient
12
    @notice Tokens can be swept to owner address by authorized user
13
 */
14
contract FeeRecipient is Ownable, AuthNoOwner {
15
    using SafeERC20 for IERC20;
16
    // --- Events ---
17
    event SweepTokenSuccess(address indexed _token, uint256 _amount, address indexed _recipient);
18

                            
                        
19
    // --- Data ---
20
    string public constant NAME = "FeeRecipient";
21

                            
                        
22
    constructor(address _ownerAddress, address _authorityAddress) {
23
        _transferOwnership(_ownerAddress);
24
        _initializeAuthority(_authorityAddress);
25
    }
26

                            
                        
27
    // === Governed Functions === //
28

                            
                        
29
    /// @dev Function to move unintended dust that are not protected
30
    /// @notice moves given amount of given token (collateral is NOT allowed)
31
    /// @notice because recipient are fixed, this function is safe to be called by anyone
32
    function sweepToken(address token, uint256 amount) public requiresAuth {
33
        uint256 balance = IERC20(token).balanceOf(address(this));
34
        require(amount <= balance, "FeeRecipient: Attempt to sweep more than balance");
35

                            
                        
36
        address _owner = owner();
37
        IERC20(token).safeTransfer(_owner, amount);
38

                            
                        
39
        emit SweepTokenSuccess(token, amount, _owner);
40
    }
41
}
42

                            
                        

Lines covered: 4 / 66 (6.1%)

1
// SPDX-License-Identifier: AGPL-3.0-only
2
pragma solidity 0.8.17;
3

                            
                        
4
import {EnumerableSet} from "./Dependencies/EnumerableSet.sol";
5
import {Authority} from "./Dependencies/Auth.sol";
6
import {RolesAuthority} from "./Dependencies/RolesAuthority.sol";
7

                            
                        
8
/// @notice Role based Authority that supports up to 256 roles.
9
/// @notice We have taken the tradeoff of additional storage usage for easier readabiliy without using off-chain / indexing services.
10
/// @author BadgerDAO Expanded from Solmate RolesAuthority
11
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/authorities/RolesAuthority.sol)
12
/// @author Modified from Dappsys (https://github.com/dapphub/ds-roles/blob/master/src/roles.sol)
13
contract Governor is RolesAuthority {
14
    using EnumerableSet for EnumerableSet.Bytes32Set;
15
    using EnumerableSet for EnumerableSet.AddressSet;
16

                            
                        
17
    bytes32 NO_ROLES = bytes32(0);
18

                            
                        
19
    struct Role {
20
        uint8 roleId;
21
        string roleName;
22
    }
23

                            
                        
24
    struct Capability {
25
        address target;
26
        bytes4 functionSig;
27
        uint8[] roles;
28
    }
29

                            
                        
30
    /*//////////////////////////////////////////////////////////////
31
                            STORAGE
32
    //////////////////////////////////////////////////////////////*/
33

                            
                        
34
    mapping(uint8 => string) internal roleNames;
35

                            
                        
36
    /*//////////////////////////////////////////////////////////////
37
                                 EVENTS
38
    //////////////////////////////////////////////////////////////*/
39

                            
                        
40
    event RoleNameSet(uint8 indexed role, string indexed name);
41

                            
                        
42
    /*//////////////////////////////////////////////////////////////
43
                               CONSTRUCTOR
44
    //////////////////////////////////////////////////////////////*/
45

                            
                        
46
    /// @notice The contract constructor initializes RolesAuthority with the given owner.
47
    /// @param _owner The address of the owner, who gains all permissions by default.
48
    constructor(address _owner) RolesAuthority(_owner, Authority(address(this))) {}
49

                            
                        
50
    /*//////////////////////////////////////////////////////////////
51
                            GETTERS
52
    //////////////////////////////////////////////////////////////*/
53

                            
                        
54
    /// @notice Returns a list of users that are assigned a specific role.
55
    /// @dev This function searches all users and checks if they are assigned the given role.
56
    /// @dev Intended for off-chain utility only due to inefficiency.
57
    /// @param role The role ID to find users for.
58
    /// @return usersWithRole An array of addresses that are assigned the given role.
59

                            
                        
60
    function getUsersByRole(uint8 role) external view returns (address[] memory usersWithRole) {
61
        // Search over all users: O(n) * 2
62
        uint256 count;
63
        for (uint256 i = 0; i < users.length(); i++) {
64
            address user = users.at(i);
65
            bool _canCall = doesUserHaveRole(user, role);
66
            if (_canCall) {
67
                count += 1;
68
            }
69
        }
70
        if (count > 0) {
71
            uint256 j = 0;
72
            usersWithRole = new address[](count);
73
            address[] memory _usrs = users.values();
74
            for (uint256 i = 0; i < _usrs.length; i++) {
75
                address user = _usrs[i];
76
                bool _canCall = doesUserHaveRole(user, role);
77
                if (_canCall) {
78
                    usersWithRole[j] = user;
79
                    j++;
80
                }
81
            }
82
        }
83
    }
84

                            
                        
85
    /// @notice Returns a list of roles that an address has.
86
    /// @dev This function searches all roles and checks if they are assigned to the given user.
87
    /// @dev Intended for off-chain utility only due to inefficiency.
88
    /// @param user The address of the user.
89
    /// @return rolesForUser An array of role IDs that the user has.
90

                            
                        
91
    function getRolesForUser(address user) external view returns (uint8[] memory rolesForUser) {
92
        // Enumerate over all possible roles and check if enabled
93
        uint256 count;
94
        for (uint8 i = 0; i < type(uint8).max; i++) {
95
            if (doesUserHaveRole(user, i)) {
96
                count += 1;
97
            }
98
        }
99
        if (count > 0) {
100
            uint256 j = 0;
101
            rolesForUser = new uint8[](count);
102
            for (uint8 i = 0; i < type(uint8).max; i++) {
103
                if (doesUserHaveRole(user, i)) {
104
                    rolesForUser[j] = i;
105
                    j++;
106
                }
107
            }
108
        }
109
    }
110

                            
                        
111
    function getRolesFromByteMap(bytes32 byteMap) public pure returns (uint8[] memory roleIds) {
112
        uint256 count;
113
        for (uint8 i = 0; i < type(uint8).max; i++) {
114
            bool roleEnabled = (uint256(byteMap >> i) & 1) != 0;
115
            if (roleEnabled) {
116
                count += 1;
117
            }
118
        }
119
        if (count > 0) {
120
            uint256 j = 0;
121
            roleIds = new uint8[](count);
122
            for (uint8 i = 0; i < type(uint8).max; i++) {
123
                bool roleEnabled = (uint256(byteMap >> i) & 1) != 0;
124
                if (roleEnabled) {
125
                    roleIds[j] = i;
126
                    j++;
127
                }
128
            }
129
        }
130
    }
131

                            
                        
132
    // helper function to generate bytes32 cache data for given roleIds array
133
    function getByteMapFromRoles(uint8[] memory roleIds) public pure returns (bytes32) {
134
        bytes32 _data;
135
        for (uint8 i = 0; i < roleIds.length; i++) {
136
            _data |= bytes32(1 << uint256(roleIds[i]));
137
        }
138
        return _data;
139
    }
140

                            
                        
141
    // helper function to return every authorization-enabled function signatures for given target address
142
    function getEnabledFunctionsInTarget(
143
        address _target
144
    ) public view returns (bytes4[] memory _funcs) {
145
        bytes32[] memory _sigs = enabledFunctionSigsByTarget[_target].values();
146
        if (_sigs.length > 0) {
147
            _funcs = new bytes4[](_sigs.length);
148
            for (uint256 i = 0; i < _sigs.length; ++i) {
149
                _funcs[i] = bytes4(_sigs[i]);
150
            }
151
        }
152
    }
153

                            
                        
154
    /// @notice return all role IDs that have at least one capability enabled
155
    function getActiveRoles() external view returns (Role[] memory activeRoles) {
156
        revert("Planned off-chain QOL function, not yet implemented, please ignore for audit");
157
    }
158

                            
                        
159
    // If a role exists, flip enabled
160

                            
                        
161
    // Return all roles that are enabled anywhere
162

                            
                        
163
    function getCapabilitiesForTarget(
164
        address target
165
    ) external view returns (Capability[] memory capabilities) {
166
        revert("Planned off-chain QOL function, not yet implemented, please ignore for audit");
167
    }
168

                            
                        
169
    function getCapabilitiesByRole(
170
        uint8 role
171
    ) external view returns (Capability[] memory capabilities) {
172
        revert("Planned off-chain QOL function, not yet implemented, please ignore for audit");
173
    }
174

                            
                        
175
    function getRoleName(uint8 role) external view returns (string memory roleName) {
176
        return roleNames[role];
177
    }
178

                            
                        
179
    /*//////////////////////////////////////////////////////////////
180
                            AUTHORIZED SETTERS
181
    //////////////////////////////////////////////////////////////*/
182

                            
                        
183
    function setRoleName(uint8 role, string memory roleName) external requiresAuth {
184
        // TODO: require maximum size for a name
185
        roleNames[role] = roleName;
186

                            
                        
187
        emit RoleNameSet(role, roleName);
188
    }
189
}
190

                            
                        

Lines covered: 3 / 91 (3.3%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
import "./Interfaces/ICdpManager.sol";
6
import "./Interfaces/ISortedCdps.sol";
7
import "./Dependencies/EbtcBase.sol";
8

                            
                        
9
contract HintHelpers is EbtcBase {
10
    string public constant NAME = "HintHelpers";
11

                            
                        
12
    ISortedCdps public immutable sortedCdps;
13
    ICdpManager public immutable cdpManager;
14

                            
                        
15
    // --- Events ---
16

                            
                        
17
    struct LocalVariables_getRedemptionHints {
18
        uint256 remainingEbtcToRedeem;
19
        uint256 minNetDebtInBTC;
20
        bytes32 currentCdpId;
21
        address currentCdpUser;
22
    }
23

                            
                        
24
    // --- Dependency setters ---
25
    constructor(
26
        address _sortedCdpsAddress,
27
        address _cdpManagerAddress,
28
        address _collateralAddress,
29
        address _activePoolAddress,
30
        address _priceFeedAddress
31
    ) EbtcBase(_activePoolAddress, _priceFeedAddress, _collateralAddress) {
32
        sortedCdps = ISortedCdps(_sortedCdpsAddress);
33
        cdpManager = ICdpManager(_cdpManagerAddress);
34
    }
35

                            
                        
36
    // --- Functions ---
37

                            
                        
38
    /**
39
     * @notice Get the redemption hints for the specified amount of eBTC, price and maximum number of iterations.
40
     * @param _EBTCamount The amount of eBTC to be redeemed.
41
     * @param _price The current price of the asset.
42
     * @param _maxIterations The maximum number of iterations to be performed.
43
     * @return firstRedemptionHint The identifier of the first CDP to be considered for redemption.
44
     * @return partialRedemptionHintNICR The new Nominal Collateral Ratio (NICR) of the CDP after partial redemption.
45
     * @return truncatedEBTCamount The actual amount of eBTC that can be redeemed.
46
     * @return partialRedemptionNewColl The new collateral amount after partial redemption.
47
     */
48
    function getRedemptionHints(
49
        uint256 _EBTCamount,
50
        uint256 _price,
51
        uint256 _maxIterations
52
    )
53
        external
54
        view
55
        returns (
56
            bytes32 firstRedemptionHint,
57
            uint256 partialRedemptionHintNICR,
58
            uint256 truncatedEBTCamount,
59
            uint256 partialRedemptionNewColl
60
        )
61
    {
62
        LocalVariables_getRedemptionHints memory vars;
63
        {
64
            vars.remainingEbtcToRedeem = _EBTCamount;
65
            vars.currentCdpId = sortedCdps.getLast();
66
            vars.currentCdpUser = sortedCdps.getOwnerAddress(vars.currentCdpId);
67

                            
                        
68
            while (
69
                vars.currentCdpUser != address(0) &&
70
                cdpManager.getICR(vars.currentCdpId, _price) < MCR
71
            ) {
72
                vars.currentCdpId = sortedCdps.getPrev(vars.currentCdpId);
73
                vars.currentCdpUser = sortedCdps.getOwnerAddress(vars.currentCdpId);
74
            }
75
            firstRedemptionHint = vars.currentCdpId;
76
        }
77

                            
                        
78
        if (_maxIterations == 0) {
79
            _maxIterations = type(uint256).max;
80
        }
81

                            
                        
82
        // Underflow is intentionally used in _maxIterations-- > 0
83
        unchecked {
84
            while (
85
                vars.currentCdpUser != address(0) &&
86
                vars.remainingEbtcToRedeem > 0 &&
87
                _maxIterations-- > 0
88
            ) {
89
                // Apply pending debt
90
                uint256 currentCdpDebt = cdpManager.getCdpDebt(vars.currentCdpId) +
91
                    cdpManager.getPendingRedistributedDebt(vars.currentCdpId);
92

                            
                        
93
                // If this CDP has more debt than the remaining to redeem, attempt a partial redemption
94
                if (currentCdpDebt > vars.remainingEbtcToRedeem) {
95
                    uint256 _cachedEbtcToRedeem = vars.remainingEbtcToRedeem;
96
                    (
97
                        partialRedemptionNewColl,
98
                        partialRedemptionHintNICR
99
                    ) = _calculateCdpStateAfterPartialRedemption(vars, currentCdpDebt, _price);
100

                            
                        
101
                    // If the partial redemption would leave the CDP with less than the minimum allowed coll, bail out of partial redemption and return only the fully redeemable
102
                    // TODO: This seems to return the original coll? why?
103
                    if (collateral.getPooledEthByShares(partialRedemptionNewColl) < MIN_NET_COLL) {
104
                        partialRedemptionHintNICR = 0; //reset to 0 as there is no partial redemption in this case
105
                        partialRedemptionNewColl = 0;
106
                        vars.remainingEbtcToRedeem = _cachedEbtcToRedeem;
107
                    } else {
108
                        vars.remainingEbtcToRedeem = 0;
109
                    }
110
                    break;
111
                } else {
112
                    vars.remainingEbtcToRedeem = vars.remainingEbtcToRedeem - currentCdpDebt;
113
                }
114

                            
                        
115
                vars.currentCdpId = sortedCdps.getPrev(vars.currentCdpId);
116
                vars.currentCdpUser = sortedCdps.getOwnerAddress(vars.currentCdpId);
117
            }
118
        }
119

                            
                        
120
        truncatedEBTCamount = _EBTCamount - vars.remainingEbtcToRedeem;
121
    }
122

                            
                        
123
    /**
124
     * @notice Calculate the partial redemption information.
125
     * @dev This is an internal function used by getRedemptionHints.
126
     * @param vars The local variables of the getRedemptionHints function.
127
     * @param currentCdpDebt The net eBTC debt of the CDP.
128
     * @param _price The current price of the asset.
129
     * @return newCollShare The new collateral share amount after partial redemption.
130
     * @return newNICR The new Nominal Collateral Ratio (NICR) of the CDP after partial redemption.
131
     */
132
    function _calculateCdpStateAfterPartialRedemption(
133
        LocalVariables_getRedemptionHints memory vars,
134
        uint256 currentCdpDebt,
135
        uint256 _price
136
    ) internal view returns (uint256, uint256) {
137
        // maxReemable = min(remainingToRedeem, currentDebt)
138
        uint256 maxRedeemableEBTC = EbtcMath._min(vars.remainingEbtcToRedeem, currentCdpDebt);
139

                            
                        
140
        uint256 newCollShare;
141
        uint256 _oldIndex = cdpManager.stEthIndex();
142
        uint256 _newIndex = collateral.getPooledEthByShares(DECIMAL_PRECISION);
143

                            
                        
144
        if (_oldIndex < _newIndex) {
145
            newCollShare = _getCollateralWithSplitFeeApplied(
146
                vars.currentCdpId,
147
                _newIndex,
148
                _oldIndex
149
            );
150
        } else {
151
            (, newCollShare, ) = cdpManager.getDebtAndCollShares(vars.currentCdpId);
152
        }
153

                            
                        
154
        vars.remainingEbtcToRedeem = vars.remainingEbtcToRedeem - maxRedeemableEBTC;
155
        uint256 collShareToReceive = collateral.getSharesByPooledEth(
156
            (maxRedeemableEBTC * DECIMAL_PRECISION) / _price
157
        );
158

                            
                        
159
        uint256 _newCollShareAfter = newCollShare - collShareToReceive;
160
        return (
161
            _newCollShareAfter,
162
            EbtcMath._computeNominalCR(_newCollShareAfter, currentCdpDebt - maxRedeemableEBTC)
163
        );
164
    }
165

                            
                        
166
    /**
167
     * @notice Get the collateral amount of a CDP after applying split fee.
168
     * @dev This is an internal function used by _calculateCdpStateAfterPartialRedemption.
169
     * @param _cdpId The identifier of the CDP.
170
     * @param _newIndex The new index after the split fee is applied.
171
     * @param _oldIndex The old index before the split fee is applied.
172
     * @return newCollShare The new collateral share amount of the CDP after applying split fee.
173
     */
174
    function _getCollateralWithSplitFeeApplied(
175
        bytes32 _cdpId,
176
        uint256 _newIndex,
177
        uint256 _oldIndex
178
    ) internal view returns (uint256) {
179
        uint256 _deltaFeePerUnit;
180
        uint256 _newStFeePerUnit;
181
        uint256 _perUnitError;
182
        uint256 _feeTaken;
183

                            
                        
184
        (_feeTaken, _deltaFeePerUnit, _perUnitError) = cdpManager.calcFeeUponStakingReward(
185
            _newIndex,
186
            _oldIndex
187
        );
188
        _newStFeePerUnit = _deltaFeePerUnit + cdpManager.systemStEthFeePerUnitIndex();
189
        (, uint256 newCollShare) = cdpManager.getAccumulatedFeeSplitApplied(
190
            _cdpId,
191
            _newStFeePerUnit
192
        );
193
        return newCollShare;
194
    }
195

                            
                        
196
    /* getApproxHint() - return address of a Cdp that is, on average, (length / numTrials) positions away in the 
197
    sortedCdps list from the correct insert position of the Cdp to be inserted. 
198
    
199
    Note: The output address is worst-case O(n) positions away from the correct insert position, however, the function 
200
    is probabilistic. Input can be tuned to guarantee results to a high degree of confidence, e.g:
201

                            
                        
202
    Submitting numTrials = k * sqrt(length), with k = 15 makes it very, very likely that the ouput address will 
203
    be <= sqrt(length) positions away from the correct insert position.
204
    */
205
    function getApproxHint(
206
        uint256 _CR,
207
        uint256 _numTrials,
208
        uint256 _inputRandomSeed
209
    ) external view returns (bytes32 hint, uint256 diff, uint256 latestRandomSeed) {
210
        uint256 arrayLength = cdpManager.getActiveCdpsCount();
211

                            
                        
212
        if (arrayLength == 0) {
213
            return (sortedCdps.nonExistId(), 0, _inputRandomSeed);
214
        }
215

                            
                        
216
        hint = sortedCdps.getLast();
217
        diff = EbtcMath._getAbsoluteDifference(_CR, cdpManager.getNominalICR(hint));
218
        latestRandomSeed = _inputRandomSeed;
219

                            
                        
220
        uint256 i = 1;
221

                            
                        
222
        while (i < _numTrials) {
223
            latestRandomSeed = uint256(keccak256(abi.encodePacked(latestRandomSeed)));
224

                            
                        
225
            uint256 arrayIndex = latestRandomSeed % arrayLength;
226
            bytes32 _cId = cdpManager.getIdFromCdpIdsArray(arrayIndex);
227
            uint256 currentNICR = cdpManager.getNominalICR(_cId);
228

                            
                        
229
            // check if abs(current - CR) > abs(closest - CR), and update closest if current is closer
230
            uint256 currentDiff = EbtcMath._getAbsoluteDifference(currentNICR, _CR);
231

                            
                        
232
            if (currentDiff < diff) {
233
                diff = currentDiff;
234
                hint = _cId;
235
            }
236
            i++;
237
        }
238
    }
239

                            
                        
240
    /// @notice Compute nominal CR for a specified collateral and debt amount
241
    function computeNominalCR(uint256 _coll, uint256 _debt) external pure returns (uint256) {
242
        return EbtcMath._computeNominalCR(_coll, _debt);
243
    }
244

                            
                        
245
    /// @notice Compute CR for a specified collateral and debt amount
246
    function computeCR(
247
        uint256 _coll,
248
        uint256 _debt,
249
        uint256 _price
250
    ) external pure returns (uint256) {
251
        return EbtcMath._computeCR(_coll, _debt, _price);
252
    }
253
}
254

                            
                        

Lines covered: 0 / 0 (0.0%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
import "./IPool.sol";
6

                            
                        
7
interface IActivePool is IPool {
8
    // --- Events ---
9
    event ActivePoolEBTCDebtUpdated(uint256 _EBTCDebt);
10
    event SystemCollSharesUpdated(uint256 _coll);
11
    event FeeRecipientAddressChanged(address _feeRecipientAddress);
12
    event FeeRecipientClaimableCollSharesIncreased(uint256 _coll, uint256 _fee);
13
    event FeeRecipientClaimableCollSharesDecreased(uint256 _coll, uint256 _fee);
14
    event FlashLoanSuccess(address _receiver, address _token, uint256 _amount, uint256 _fee);
15
    event SweepTokenSuccess(address indexed _token, uint256 _amount, address indexed _recipient);
16

                            
                        
17
    // --- Functions ---
18
    function transferSystemCollShares(address _account, uint256 _amount) external;
19

                            
                        
20
    function increaseSystemCollShares(uint256 _value) external;
21

                            
                        
22
    function transferSystemCollSharesAndLiquidatorReward(
23
        address _account,
24
        uint256 _shares,
25
        uint256 _liquidatorRewardShares
26
    ) external;
27

                            
                        
28
    function allocateSystemCollSharesToFeeRecipient(uint256 _shares) external;
29

                            
                        
30
    function claimFeeRecipientCollShares(uint256 _shares) external;
31

                            
                        
32
    function feeRecipientAddress() external view returns (address);
33

                            
                        
34
    function getFeeRecipientClaimableCollShares() external view returns (uint256);
35

                            
                        
36
    function setFeeRecipientAddress(address _feeRecipientAddress) external;
37
}
38

                            
                        

Lines covered: 0 / 0 (0.0%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4
import "./IPositionManagers.sol";
5

                            
                        
6
// Common interface for the Cdp Manager.
7
interface IBorrowerOperations is IPositionManagers {
8
    // --- Events ---
9

                            
                        
10
    event FeeRecipientAddressChanged(address _feeRecipientAddress);
11
    event FlashLoanSuccess(address _receiver, address _token, uint256 _amount, uint256 _fee);
12

                            
                        
13
    // --- Functions ---
14

                            
                        
15
    function openCdp(
16
        uint256 _EBTCAmount,
17
        bytes32 _upperHint,
18
        bytes32 _lowerHint,
19
        uint256 _stEthBalance
20
    ) external returns (bytes32);
21

                            
                        
22
    function openCdpFor(
23
        uint _EBTCAmount,
24
        bytes32 _upperHint,
25
        bytes32 _lowerHint,
26
        uint _collAmount,
27
        address _borrower
28
    ) external returns (bytes32);
29

                            
                        
30
    function addColl(
31
        bytes32 _cdpId,
32
        bytes32 _upperHint,
33
        bytes32 _lowerHint,
34
        uint256 _stEthBalanceIncrease
35
    ) external;
36

                            
                        
37
    function withdrawColl(
38
        bytes32 _cdpId,
39
        uint256 _stEthBalanceDecrease,
40
        bytes32 _upperHint,
41
        bytes32 _lowerHint
42
    ) external;
43

                            
                        
44
    function withdrawDebt(
45
        bytes32 _cdpId,
46
        uint256 _amount,
47
        bytes32 _upperHint,
48
        bytes32 _lowerHint
49
    ) external;
50

                            
                        
51
    function repayDebt(
52
        bytes32 _cdpId,
53
        uint256 _amount,
54
        bytes32 _upperHint,
55
        bytes32 _lowerHint
56
    ) external;
57

                            
                        
58
    function closeCdp(bytes32 _cdpId) external;
59

                            
                        
60
    function adjustCdp(
61
        bytes32 _cdpId,
62
        uint256 _stEthBalanceDecrease,
63
        uint256 _debtChange,
64
        bool isDebtIncrease,
65
        bytes32 _upperHint,
66
        bytes32 _lowerHint
67
    ) external;
68

                            
                        
69
    function adjustCdpWithColl(
70
        bytes32 _cdpId,
71
        uint256 _stEthBalanceDecrease,
72
        uint256 _debtChange,
73
        bool isDebtIncrease,
74
        bytes32 _upperHint,
75
        bytes32 _lowerHint,
76
        uint256 _stEthBalanceIncrease
77
    ) external;
78

                            
                        
79
    function claimSurplusCollShares() external;
80

                            
                        
81
    function feeRecipientAddress() external view returns (address);
82
}
83

                            
                        

Lines covered: 0 / 0 (0.0%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
import "./IEbtcBase.sol";
6
import "./ICdpManagerData.sol";
7

                            
                        
8
// Common interface for the Cdp Manager.
9
interface ICdpManager is IEbtcBase, ICdpManagerData {
10
    // --- Functions ---
11
    function getActiveCdpsCount() external view returns (uint256);
12

                            
                        
13
    function getIdFromCdpIdsArray(uint256 _index) external view returns (bytes32);
14

                            
                        
15
    function liquidate(bytes32 _cdpId) external;
16

                            
                        
17
    function partiallyLiquidate(
18
        bytes32 _cdpId,
19
        uint256 _partialAmount,
20
        bytes32 _upperPartialHint,
21
        bytes32 _lowerPartialHint
22
    ) external;
23

                            
                        
24
    function batchLiquidateCdps(bytes32[] calldata _cdpArray) external;
25

                            
                        
26
    function redeemCollateral(
27
        uint256 _EBTCAmount,
28
        bytes32 _firstRedemptionHint,
29
        bytes32 _upperPartialRedemptionHint,
30
        bytes32 _lowerPartialRedemptionHint,
31
        uint256 _partialRedemptionHintNICR,
32
        uint256 _maxIterations,
33
        uint256 _maxFee
34
    ) external;
35

                            
                        
36
    function updateStakeAndTotalStakes(bytes32 _cdpId) external returns (uint256);
37

                            
                        
38
    function syncAccounting(bytes32 _cdpId) external;
39

                            
                        
40
    function getTotalStakeForFeeTaken(uint256 _feeTaken) external view returns (uint256, uint256);
41

                            
                        
42
    function closeCdp(bytes32 _cdpId, address _borrower, uint256 _debt, uint256 _coll) external;
43

                            
                        
44
    function getRedemptionRate() external view returns (uint256);
45

                            
                        
46
    function getRedemptionRateWithDecay() external view returns (uint256);
47

                            
                        
48
    function getRedemptionFeeWithDecay(uint256 _ETHDrawn) external view returns (uint256);
49

                            
                        
50
    function decayBaseRateFromBorrowing() external;
51

                            
                        
52
    function getCdpStatus(bytes32 _cdpId) external view returns (uint256);
53

                            
                        
54
    function getCdpStake(bytes32 _cdpId) external view returns (uint256);
55

                            
                        
56
    function getCdpDebt(bytes32 _cdpId) external view returns (uint256);
57

                            
                        
58
    function getCdpCollShares(bytes32 _cdpId) external view returns (uint256);
59

                            
                        
60
    function getCdpLiquidatorRewardShares(bytes32 _cdpId) external view returns (uint);
61

                            
                        
62
    function initializeCdp(
63
        bytes32 _cdpId,
64
        uint256 _debt,
65
        uint256 _coll,
66
        uint256 _liquidatorRewardShares,
67
        address _borrower
68
    ) external;
69

                            
                        
70
    function updateCdp(
71
        bytes32 _cdpId,
72
        address _borrower,
73
        uint256 _coll,
74
        uint256 _debt,
75
        uint256 _newColl,
76
        uint256 _newDebt
77
    ) external;
78

                            
                        
79
    function getTCR(uint256 _price) external view returns (uint256);
80

                            
                        
81
    function checkRecoveryMode(uint256 _price) external view returns (bool);
82
}
83

                            
                        

Lines covered: 0 / 0 (0.0%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
import "./ICollSurplusPool.sol";
6
import "./IEBTCToken.sol";
7
import "./ISortedCdps.sol";
8
import "./IActivePool.sol";
9
import "./IRecoveryModeGracePeriod.sol";
10
import "../Dependencies/ICollateralTokenOracle.sol";
11

                            
                        
12
// Common interface for the Cdp Manager.
13
interface ICdpManagerData is IRecoveryModeGracePeriod {
14
    // --- Events ---
15

                            
                        
16
    event FeeRecipientAddressChanged(address _feeRecipientAddress);
17
    event StakingRewardSplitSet(uint256 _stakingRewardSplit);
18
    event RedemptionFeeFloorSet(uint256 _redemptionFeeFloor);
19
    event MinuteDecayFactorSet(uint256 _minuteDecayFactor);
20
    event BetaSet(uint256 _beta);
21
    event RedemptionsPaused(bool _paused);
22

                            
                        
23
    event Liquidation(uint256 _liquidatedDebt, uint256 _liquidatedColl, uint256 _liqReward);
24
    event Redemption(
25
        uint256 _debtToRedeemExpected,
26
        uint256 _debtToRedeemActual,
27
        uint256 _collSharesSent,
28
        uint256 _feeCollShares,
29
        address _redeemer
30
    );
31
    event CdpUpdated(
32
        bytes32 indexed _cdpId,
33
        address indexed _borrower,
34
        uint256 _oldDebt,
35
        uint256 _oldCollShares,
36
        uint256 _debt,
37
        uint256 _collShares,
38
        uint256 _stake,
39
        CdpOperation _operation
40
    );
41
    event CdpLiquidated(
42
        bytes32 indexed _cdpId,
43
        address indexed _borrower,
44
        uint _debt,
45
        uint _collShares,
46
        CdpOperation _operation,
47
        address _liquidator,
48
        uint _premiumToLiquidator
49
    );
50
    event CdpPartiallyLiquidated(
51
        bytes32 indexed _cdpId,
52
        address indexed _borrower,
53
        uint256 _debt,
54
        uint256 _collShares,
55
        CdpOperation operation,
56
        address _liquidator,
57
        uint _premiumToLiquidator
58
    );
59
    event BaseRateUpdated(uint256 _baseRate);
60
    event LastRedemptionTimestampUpdated(uint256 _lastFeeOpTime);
61
    event TotalStakesUpdated(uint256 _newTotalStakes);
62
    event SystemSnapshotsUpdated(uint256 _totalStakesSnapshot, uint256 _totalCollateralSnapshot);
63
    event SystemDebtRedistributionIndexUpdated(uint256 _systemDebtRedistributionIndex);
64
    event CdpDebtRedistributionIndexUpdated(bytes32 _cdpId, uint256 _cdpDebtRedistributionIndex);
65
    event CdpArrayIndexUpdated(bytes32 _cdpId, uint256 _newIndex);
66
    event StEthIndexUpdated(uint256 _oldIndex, uint256 _newIndex, uint256 _updTimestamp);
67
    event CollateralFeePerUnitUpdated(uint256 _oldPerUnit, uint256 _newPerUnit, uint256 _feeTaken);
68
    event CdpFeeSplitApplied(
69
        bytes32 _cdpId,
70
        uint256 _oldPerUnitCdp,
71
        uint256 _newPerUnitCdp,
72
        uint256 _collReduced,
73
        uint256 collLeft
74
    );
75

                            
                        
76
    enum CdpOperation {
77
        openCdp,
78
        closeCdp,
79
        adjustCdp,
80
        syncAccounting,
81
        liquidateInNormalMode,
82
        liquidateInRecoveryMode,
83
        redeemCollateral,
84
        partiallyLiquidate
85
    }
86

                            
                        
87
    enum Status {
88
        nonExistent,
89
        active,
90
        closedByOwner,
91
        closedByLiquidation,
92
        closedByRedemption
93
    }
94

                            
                        
95
    // Store the necessary data for a cdp
96
    struct Cdp {
97
        uint256 debt;
98
        uint256 coll;
99
        uint256 stake;
100
        uint256 liquidatorRewardShares;
101
        Status status;
102
        uint128 arrayIndex;
103
    }
104

                            
                        
105
    /*
106
     * --- Variable container structs for liquidations ---
107
     *
108
     * These structs are used to hold, return and assign variables inside the liquidation functions,
109
     * in order to avoid the error: "CompilerError: Stack too deep".
110
     **/
111

                            
                        
112
    struct CdpDebtAndCollShares {
113
        uint256 entireDebt;
114
        uint256 entireColl;
115
        uint256 pendingDebtReward;
116
    }
117

                            
                        
118
    struct LiquidationLocals {
119
        bytes32 cdpId;
120
        uint256 partialAmount; // used only for partial liquidation, default 0 means full liquidation
121
        uint256 price;
122
        uint256 ICR;
123
        bytes32 upperPartialHint;
124
        bytes32 lowerPartialHint;
125
        bool recoveryModeAtStart;
126
        uint256 TCR;
127
        uint256 totalSurplusCollShares;
128
        uint256 totalCollSharesToSend;
129
        uint256 totalDebtToBurn;
130
        uint256 totalDebtToRedistribute;
131
        uint256 totalLiquidatorRewardCollShares;
132
    }
133

                            
                        
134
    struct LiquidationRecoveryModeLocals {
135
        uint256 entireSystemDebt;
136
        uint256 entireSystemColl;
137
        uint256 totalDebtToBurn;
138
        uint256 totalCollSharesToSend;
139
        uint256 totalSurplusCollShares;
140
        bytes32 cdpId;
141
        uint256 price;
142
        uint256 ICR;
143
        uint256 totalDebtToRedistribute;
144
        uint256 totalLiquidatorRewardCollShares;
145
    }
146

                            
                        
147
    struct LocalVariables_OuterLiquidationFunction {
148
        uint256 price;
149
        bool recoveryModeAtStart;
150
        uint256 liquidatedDebt;
151
        uint256 liquidatedColl;
152
    }
153

                            
                        
154
    struct LocalVariables_LiquidationSequence {
155
        uint256 i;
156
        uint256 ICR;
157
        bytes32 cdpId;
158
        bool backToNormalMode;
159
        uint256 entireSystemDebt;
160
        uint256 entireSystemColl;
161
        uint256 price;
162
        uint256 TCR;
163
    }
164

                            
                        
165
    struct SingleRedemptionInputs {
166
        bytes32 cdpId;
167
        uint256 maxEBTCamount;
168
        uint256 price;
169
        bytes32 upperPartialRedemptionHint;
170
        bytes32 lowerPartialRedemptionHint;
171
        uint256 partialRedemptionHintNICR;
172
    }
173

                            
                        
174
    struct LiquidationValues {
175
        uint256 entireCdpDebt;
176
        uint256 debtToBurn;
177
        uint256 totalCollToSendToLiquidator;
178
        uint256 debtToRedistribute;
179
        uint256 collSurplus;
180
        uint256 liquidatorCollSharesReward;
181
    }
182

                            
                        
183
    struct LiquidationTotals {
184
        uint256 totalDebtInSequence;
185
        uint256 totalDebtToBurn;
186
        uint256 totalCollToSendToLiquidator;
187
        uint256 totalDebtToRedistribute;
188
        uint256 totalCollSurplus;
189
        uint256 totalCollReward;
190
    }
191

                            
                        
192
    // --- Variable container structs for redemptions ---
193

                            
                        
194
    struct RedemptionTotals {
195
        uint256 remainingDebtToRedeem;
196
        uint256 debtToRedeem;
197
        uint256 collSharesDrawn;
198
        uint256 totalCollSharesSurplus;
199
        uint256 feeCollShares;
200
        uint256 collSharesToRedeemer;
201
        uint256 decayedBaseRate;
202
        uint256 price;
203
        uint256 systemDebtAtStart;
204
        uint256 systemCollSharesAtStart;
205
        uint256 tcrAtStart;
206
    }
207

                            
                        
208
    struct SingleRedemptionValues {
209
        uint256 debtToRedeem;
210
        uint256 collSharesDrawn;
211
        uint256 collSurplus;
212
        uint256 liquidatorRewardShares;
213
        bool cancelledPartial;
214
        bool fullRedemption;
215
    }
216

                            
                        
217
    function totalStakes() external view returns (uint256);
218

                            
                        
219
    function ebtcToken() external view returns (IEBTCToken);
220

                            
                        
221
    function systemStEthFeePerUnitIndex() external view returns (uint256);
222

                            
                        
223
    function systemStEthFeePerUnitIndexError() external view returns (uint256);
224

                            
                        
225
    function stEthIndex() external view returns (uint256);
226

                            
                        
227
    function calcFeeUponStakingReward(
228
        uint256 _newIndex,
229
        uint256 _prevIndex
230
    ) external view returns (uint256, uint256, uint256);
231

                            
                        
232
    function syncGlobalAccounting() external; // Accrues StEthFeeSplit without influencing Grace Period
233

                            
                        
234
    function syncGlobalAccountingAndGracePeriod() external; // Accrues StEthFeeSplit and influences Grace Period
235

                            
                        
236
    function getAccumulatedFeeSplitApplied(
237
        bytes32 _cdpId,
238
        uint256 _systemStEthFeePerUnitIndex
239
    ) external view returns (uint256, uint256);
240

                            
                        
241
    function getNominalICR(bytes32 _cdpId) external view returns (uint256);
242

                            
                        
243
    function getICR(bytes32 _cdpId, uint256 _price) external view returns (uint256);
244

                            
                        
245
    function getSyncedCdpDebt(bytes32 _cdpId) external view returns (uint256);
246

                            
                        
247
    function getSyncedCdpCollShares(bytes32 _cdpId) external view returns (uint256);
248

                            
                        
249
    function getSyncedICR(bytes32 _cdpId, uint256 _price) external view returns (uint256);
250

                            
                        
251
    function getSyncedTCR(uint256 _price) external view returns (uint256);
252

                            
                        
253
    function getPendingRedistributedDebt(bytes32 _cdpId) external view returns (uint256);
254

                            
                        
255
    function hasPendingRedistributedDebt(bytes32 _cdpId) external view returns (bool);
256

                            
                        
257
    function getDebtAndCollShares(
258
        bytes32 _cdpId
259
    ) external view returns (uint256 debt, uint256 coll, uint256 pendingEBTCDebtReward);
260

                            
                        
261
    function canLiquidateRecoveryMode(uint256 icr, uint256 tcr) external view returns (bool);
262
}
263

                            
                        

Lines covered: 0 / 0 (0.0%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
interface ICollSurplusPool {
6
    // --- Events ---
7

                            
                        
8
    event SurplusCollSharesUpdated(address indexed _account, uint256 _newBalance);
9
    event CollSharesTransferred(address _to, uint256 _amount);
10

                            
                        
11
    event SweepTokenSuccess(address indexed _token, uint256 _amount, address indexed _recipient);
12

                            
                        
13
    // --- Contract setters ---
14

                            
                        
15
    function getTotalSurplusCollShares() external view returns (uint256);
16

                            
                        
17
    function getSurplusCollShares(address _account) external view returns (uint256);
18

                            
                        
19
    function increaseSurplusCollShares(address _account, uint256 _amount) external;
20

                            
                        
21
    function claimSurplusCollShares(address _account) external;
22

                            
                        
23
    function increaseTotalSurplusCollShares(uint256 _value) external;
24
}
25

                            
                        

Lines covered: 0 / 0 (0.0%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
import "../Dependencies/IERC20.sol";
6
import "../Dependencies/IERC2612.sol";
7

                            
                        
8
interface IEBTCToken is IERC20, IERC2612 {
9
    // --- Functions ---
10

                            
                        
11
    function mint(address _account, uint256 _amount) external;
12

                            
                        
13
    function burn(address _account, uint256 _amount) external;
14
}
15

                            
                        

Lines covered: 0 / 0 (0.0%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
interface IERC3156FlashBorrower {
6
    /**
7
     * @dev Receive a flash loan.
8
     * @param initiator The initiator of the loan.
9
     * @param token The loan currency.
10
     * @param amount The amount of tokens lent.
11
     * @param fee The additional amount of tokens to repay.
12
     * @param data Arbitrary data structure, intended to contain user-defined parameters.
13
     * @return The keccak256 hash of "ERC3156FlashBorrower.onFlashLoan"
14
     */
15
    function onFlashLoan(
16
        address initiator,
17
        address token,
18
        uint256 amount,
19
        uint256 fee,
20
        bytes calldata data
21
    ) external returns (bytes32);
22
}
23

                            
                        

Lines covered: 0 / 0 (0.0%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
import "./IERC3156FlashBorrower.sol";
6

                            
                        
7
interface IERC3156FlashLender {
8
    event FlashFeeSet(address _setter, uint256 _oldFee, uint256 _newFee);
9
    event MaxFlashFeeSet(address _setter, uint256 _oldMaxFee, uint256 _newMaxFee);
10
    event FlashLoansPaused(address _setter, bool _paused);
11

                            
                        
12
    /**
13
     * @dev The amount of currency available to be lent.
14
     * @param token The loan currency.
15
     * @return The amount of `token` that can be borrowed.
16
     */
17
    function maxFlashLoan(address token) external view returns (uint256);
18

                            
                        
19
    /**
20
     * @dev The fee to be charged for a given loan.
21
     * @param token The loan currency.
22
     * @param amount The amount of tokens lent.
23
     * @return The amount of `token` to be charged for the loan, on top of the returned principal.
24
     */
25
    function flashFee(address token, uint256 amount) external view returns (uint256);
26

                            
                        
27
    /**
28
     * @dev Initiate a flash loan.
29
     * @param receiver The receiver of the tokens in the loan, and the receiver of the callback.
30
     * @param token The loan currency.
31
     * @param amount The amount of tokens lent.
32
     * @param data Arbitrary data structure, intended to contain user-defined parameters.
33
     */
34
    function flashLoan(
35
        IERC3156FlashBorrower receiver,
36
        address token,
37
        uint256 amount,
38
        bytes calldata data
39
    ) external returns (bool);
40
}
41

                            
                        

Lines covered: 0 / 0 (0.0%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
import "./IPriceFeed.sol";
6

                            
                        
7
interface IEbtcBase {
8
    function priceFeed() external view returns (IPriceFeed);
9
}
10

                            
                        

Lines covered: 0 / 0 (0.0%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
interface IFallbackCaller {
6
    // --- Events ---
7
    event FallbackTimeOutChanged(uint256 _oldTimeOut, uint256 _newTimeOut);
8

                            
                        
9
    // --- Function External View ---
10

                            
                        
11
    // NOTE: The fallback oracle must always return its answer scaled to 18 decimals where applicable
12
    //       The system will assume an 18 decimal response for efficiency.
13
    function getFallbackResponse() external view returns (uint256, uint256, bool);
14

                            
                        
15
    // NOTE: this returns the timeout window interval for the fallback oracle instead
16
    // of storing in the `PriceFeed` contract is retrieve for the `FallbackCaller`
17
    function fallbackTimeout() external view returns (uint256);
18

                            
                        
19
    // --- Function External Setter ---
20

                            
                        
21
    function setFallbackTimeout(uint256 newFallbackTimeout) external;
22
}
23

                            
                        

Lines covered: 0 / 0 (0.0%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
interface IPermitNonce {
6
    // --- Functions ---
7
    function increasePermitNonce() external returns (uint256);
8

                            
                        
9
    function nonces(address owner) external view returns (uint256);
10
}
11

                            
                        

Lines covered: 0 / 0 (0.0%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
// Common interface for the Pools.
6
interface IPool {
7
    // --- Events ---
8

                            
                        
9
    event ETHBalanceUpdated(uint256 _newBalance);
10
    event EBTCBalanceUpdated(uint256 _newBalance);
11
    event CollSharesTransferred(address _to, uint256 _amount);
12

                            
                        
13
    // --- Functions ---
14

                            
                        
15
    function getSystemCollShares() external view returns (uint256);
16

                            
                        
17
    function getSystemDebt() external view returns (uint256);
18

                            
                        
19
    function increaseSystemDebt(uint256 _amount) external;
20

                            
                        
21
    function decreaseSystemDebt(uint256 _amount) external;
22
}
23

                            
                        

Lines covered: 0 / 0 (0.0%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
interface IPositionManagers {
6
    enum PositionManagerApproval {
7
        None,
8
        OneTime,
9
        Persistent
10
    }
11

                            
                        
12
    event PositionManagerApprovalSet(
13
        address _borrower,
14
        address _positionManager,
15
        PositionManagerApproval _approval
16
    );
17

                            
                        
18
    function getPositionManagerApproval(
19
        address _borrower,
20
        address _positionManager
21
    ) external view returns (PositionManagerApproval);
22

                            
                        
23
    function setPositionManagerApproval(
24
        address _positionManager,
25
        PositionManagerApproval _approval
26
    ) external;
27

                            
                        
28
    function revokePositionManagerApproval(address _positionManager) external;
29

                            
                        
30
    function renouncePositionManagerApproval(address _borrower) external;
31

                            
                        
32
    function permitPositionManagerApproval(
33
        address _borrower,
34
        address _positionManager,
35
        PositionManagerApproval _approval,
36
        uint _deadline,
37
        uint8 v,
38
        bytes32 r,
39
        bytes32 s
40
    ) external;
41

                            
                        
42
    function version() external view returns (string memory);
43

                            
                        
44
    function permitTypeHash() external view returns (bytes32);
45

                            
                        
46
    function domainSeparator() external view returns (bytes32);
47
}
48

                            
                        

Lines covered: 0 / 0 (0.0%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
interface IPriceFeed {
6
    // --- Events ---
7
    event LastGoodPriceUpdated(uint256 _lastGoodPrice);
8
    event PriceFeedStatusChanged(Status newStatus);
9
    event FallbackCallerChanged(address _oldFallbackCaller, address _newFallbackCaller);
10
    event UnhealthyFallbackCaller(address _fallbackCaller, uint256 timestamp);
11

                            
                        
12
    // --- Structs ---
13

                            
                        
14
    struct ChainlinkResponse {
15
        uint80 roundEthBtcId;
16
        uint80 roundStEthEthId;
17
        uint256 answer;
18
        uint256 timestampEthBtc;
19
        uint256 timestampStEthEth;
20
        bool success;
21
    }
22

                            
                        
23
    struct FallbackResponse {
24
        uint256 answer;
25
        uint256 timestamp;
26
        bool success;
27
    }
28

                            
                        
29
    // --- Enum ---
30

                            
                        
31
    enum Status {
32
        chainlinkWorking,
33
        usingFallbackChainlinkUntrusted,
34
        bothOraclesUntrusted,
35
        usingFallbackChainlinkFrozen,
36
        usingChainlinkFallbackUntrusted
37
    }
38

                            
                        
39
    // --- Function ---
40
    function fetchPrice() external returns (uint256);
41
}
42

                            
                        

Lines covered: 0 / 0 (0.0%)

1
// SPDX-License-Identifier: MIT
2
pragma solidity 0.8.17;
3

                            
                        
4
// Interface for State Updates that can trigger RM Liquidations
5
interface IRecoveryModeGracePeriod {
6
    event TCRNotified(uint256 TCR); /// NOTE: Mostly for debugging to ensure synch
7

                            
                        
8
    // NOTE: Ts is implicit in events (it's added by GETH)
9
    event GracePeriodStart();
10
    event GracePeriodEnd();
11
    event GracePeriodDurationSet(uint256 _recoveryModeGracePeriodDuration);
12

                            
                        
13
    function notifyStartGracePeriod(uint256 tcr) external;
14

                            
                        
15
    function notifyEndGracePeriod(uint256 tcr) external;
16
}
17

                            
                        

Lines covered: 0 / 0 (0.0%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
// Common interface for the SortedCdps Doubly Linked List.
6
interface ISortedCdps {
7
    // --- Events ---
8

                            
                        
9
    event NodeAdded(bytes32 _id, uint _NICR);
10
    event NodeRemoved(bytes32 _id);
11

                            
                        
12
    // --- Functions ---
13

                            
                        
14
    function remove(bytes32 _id) external;
15

                            
                        
16
    function batchRemove(bytes32[] memory _ids) external;
17

                            
                        
18
    function reInsert(bytes32 _id, uint256 _newICR, bytes32 _prevId, bytes32 _nextId) external;
19

                            
                        
20
    function contains(bytes32 _id) external view returns (bool);
21

                            
                        
22
    function isFull() external view returns (bool);
23

                            
                        
24
    function isEmpty() external view returns (bool);
25

                            
                        
26
    function getSize() external view returns (uint256);
27

                            
                        
28
    function getMaxSize() external view returns (uint256);
29

                            
                        
30
    function getFirst() external view returns (bytes32);
31

                            
                        
32
    function getLast() external view returns (bytes32);
33

                            
                        
34
    function getNext(bytes32 _id) external view returns (bytes32);
35

                            
                        
36
    function getPrev(bytes32 _id) external view returns (bytes32);
37

                            
                        
38
    function validInsertPosition(
39
        uint256 _ICR,
40
        bytes32 _prevId,
41
        bytes32 _nextId
42
    ) external view returns (bool);
43

                            
                        
44
    function findInsertPosition(
45
        uint256 _ICR,
46
        bytes32 _prevId,
47
        bytes32 _nextId
48
    ) external view returns (bytes32, bytes32);
49

                            
                        
50
    function insert(
51
        address owner,
52
        uint256 _ICR,
53
        bytes32 _prevId,
54
        bytes32 _nextId
55
    ) external returns (bytes32);
56

                            
                        
57
    function getOwnerAddress(bytes32 _id) external pure returns (address);
58

                            
                        
59
    function nonExistId() external view returns (bytes32);
60

                            
                        
61
    function cdpCountOf(address owner) external view returns (uint256);
62

                            
                        
63
    function getCdpCountOf(
64
        address owner,
65
        bytes32 startNodeId,
66
        uint maxNodes
67
    ) external view returns (uint256, bytes32);
68

                            
                        
69
    function getCdpsOf(address owner) external view returns (bytes32[] memory);
70

                            
                        
71
    function getAllCdpsOf(
72
        address owner,
73
        bytes32 startNodeId,
74
        uint maxNodes
75
    ) external view returns (bytes32[] memory, uint256, bytes32);
76

                            
                        
77
    function cdpOfOwnerByIndex(address owner, uint256 index) external view returns (bytes32);
78

                            
                        
79
    function cdpOfOwnerByIdx(
80
        address owner,
81
        uint256 index,
82
        bytes32 startNodeId,
83
        uint maxNodes
84
    ) external view returns (bytes32, bool);
85
}
86

                            
                        

Lines covered: 0 / 0 (0.0%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
interface IWETH {
6
    function deposit() external payable;
7

                            
                        
8
    function withdraw(uint256) external;
9

                            
                        
10
    function transfer(address to, uint256 amount) external returns (bool);
11

                            
                        
12
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
13
}
14

                            
                        

Lines covered: 141 / 447 (31.5%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4
import "./Interfaces/ICdpManagerData.sol";
5
import "./Interfaces/ICollSurplusPool.sol";
6
import "./Interfaces/IEBTCToken.sol";
7
import "./Interfaces/ISortedCdps.sol";
8
import "./Dependencies/ICollateralTokenOracle.sol";
9
import "./CdpManagerStorage.sol";
10

                            
                        
11
contract LiquidationLibrary is CdpManagerStorage {
12
    constructor(
13
        address _borrowerOperationsAddress,
14
        address _collSurplusPool,
15
        address _ebtcToken,
16
        address _sortedCdps,
17
        address _activePool,
18
        address _priceFeed,
19
        address _collateral
20
    )
21
        CdpManagerStorage(
22
            address(0),
23
            address(0),
24
            _borrowerOperationsAddress,
25
            _collSurplusPool,
26
            _ebtcToken,
27
            _sortedCdps,
28
            _activePool,
29
            _priceFeed,
30
            _collateral
31
        )
32
    {}
33

                            
                        
34
    /// @notice Single CDP liquidation function (fully).
35
    /// @notice callable by anyone, attempts to liquidate the CdpId. Executes successfully if Cdp meets the conditions for liquidation (e.g. in Normal Mode, it liquidates if the Cdp's ICR < the system MCR).
36
    function liquidate(bytes32 _cdpId) external nonReentrantSelfAndBOps {
37
        _liquidateIndividualCdpSetup(_cdpId, 0, _cdpId, _cdpId);
38
    }
39

                            
                        
40
    // Single CDP liquidation function (partially).
41
    function partiallyLiquidate(
42
        bytes32 _cdpId,
43
        uint256 _partialAmount,
44
        bytes32 _upperPartialHint,
45
        bytes32 _lowerPartialHint
46
    ) external nonReentrantSelfAndBOps {
47
        require(_partialAmount != 0, "LiquidationLibrary: use `liquidate` for 100%");
48
        _liquidateIndividualCdpSetup(_cdpId, _partialAmount, _upperPartialHint, _lowerPartialHint);
49
    }
50

                            
                        
51
    // Single CDP liquidation function.
52
    function _liquidateIndividualCdpSetup(
53
        bytes32 _cdpId,
54
        uint256 _partialAmount,
55
        bytes32 _upperPartialHint,
56
        bytes32 _lowerPartialHint
57
    ) internal {
58
        _requireCdpIsActive(_cdpId);
59

                            
                        
60
        _syncAccounting(_cdpId);
61

                            
                        
62
        uint256 _price = priceFeed.fetchPrice();
63

                            
                        
64
        // prepare local variables
65
        uint256 _ICR = getICR(_cdpId, _price); // @audit syncAccounting already called, guarenteed to be synced
66
        (uint256 _TCR, uint256 systemColl, uint256 systemDebt) = _getTCRWithSystemDebtAndCollShares(
67
            _price
68
        );
69

                            
                        
70
        // If CDP is above MCR
71
        if (_ICR >= MCR) {
72
            // We must be in RM
73
            require(
74
                _checkICRAgainstLiqThreshold(_ICR, _TCR),
75
                "LiquidationLibrary: ICR is not below liquidation threshold in current mode"
76
            );
77

                            
                        
78
            // == Grace Period == //
79
            uint128 cachedLastGracePeriodStartTimestamp = lastGracePeriodStartTimestamp;
80
            require(
81
                cachedLastGracePeriodStartTimestamp != UNSET_TIMESTAMP,
82
                "LiquidationLibrary: Recovery Mode grace period not started"
83
            );
84
            require(
85
                block.timestamp >
86
                    cachedLastGracePeriodStartTimestamp + recoveryModeGracePeriodDuration,
87
                "LiquidationLibrary: Recovery mode grace period still in effect"
88
            );
89
        } // Implicit Else Case, Implies ICR < MRC, meaning the CDP is liquidatable
90

                            
                        
91
        bool _recoveryModeAtStart = _TCR < CCR ? true : false;
92
        LiquidationLocals memory _liqState = LiquidationLocals(
93
            _cdpId,
94
            _partialAmount,
95
            _price,
96
            _ICR,
97
            _upperPartialHint,
98
            _lowerPartialHint,
99
            (_recoveryModeAtStart),
100
            _TCR,
101
            0,
102
            0,
103
            0,
104
            0,
105
            0
106
        );
107

                            
                        
108
        LiquidationRecoveryModeLocals memory _rs = LiquidationRecoveryModeLocals(
109
            systemDebt,
110
            systemColl,
111
            0,
112
            0,
113
            0,
114
            _cdpId,
115
            _price,
116
            _ICR,
117
            0,
118
            0
119
        );
120

                            
                        
121
        _liquidateIndividualCdpSetupCDP(_liqState, _rs);
122
    }
123

                            
                        
124
    // liquidate given CDP by repaying debt in full or partially if its ICR is below MCR or TCR in recovery mode.
125
    // For partial liquidation, caller should use HintHelper smart contract to get correct hints for reinsertion into sorted CDP list
126
    function _liquidateIndividualCdpSetupCDP(
127
        LiquidationLocals memory _liqState,
128
        LiquidationRecoveryModeLocals memory _recoveryState
129
    ) internal {
130
        LiquidationValues memory liquidationValues;
131

                            
                        
132
        uint256 startingSystemDebt = _recoveryState.entireSystemDebt;
133
        uint256 startingSystemColl = _recoveryState.entireSystemColl;
134

                            
                        
135
        if (_liqState.partialAmount == 0) {
136
            (
137
                liquidationValues.debtToBurn,
138
                liquidationValues.totalCollToSendToLiquidator,
139
                liquidationValues.debtToRedistribute,
140
                liquidationValues.liquidatorCollSharesReward,
141
                liquidationValues.collSurplus
142
            ) = _liquidateCdpInGivenMode(_liqState, _recoveryState);
143
        } else {
144
            (
145
                liquidationValues.debtToBurn,
146
                liquidationValues.totalCollToSendToLiquidator
147
            ) = _liquidateCDPPartially(_liqState);
148
            if (
149
                liquidationValues.totalCollToSendToLiquidator == 0 &&
150
                liquidationValues.debtToBurn == 0
151
            ) {
152
                // retry with fully liquidation
153
                (
154
                    liquidationValues.debtToBurn,
155
                    liquidationValues.totalCollToSendToLiquidator,
156
                    liquidationValues.debtToRedistribute,
157
                    liquidationValues.liquidatorCollSharesReward,
158
                    liquidationValues.collSurplus
159
                ) = _liquidateCdpInGivenMode(_liqState, _recoveryState);
160
            }
161
        }
162

                            
                        
163
        _finalizeLiquidation(
164
            liquidationValues.debtToBurn,
165
            liquidationValues.totalCollToSendToLiquidator,
166
            liquidationValues.debtToRedistribute,
167
            liquidationValues.liquidatorCollSharesReward,
168
            liquidationValues.collSurplus,
169
            startingSystemColl,
170
            startingSystemDebt,
171
            _liqState.price
172
        );
173
    }
174

                            
                        
175
    // liquidate (and close) the CDP from an external liquidator
176
    // this function would return the liquidated debt and collateral of the given CDP
177
    function _liquidateCdpInGivenMode(
178
        LiquidationLocals memory _liqState,
179
        LiquidationRecoveryModeLocals memory _recoveryState
180
    ) private returns (uint256, uint256, uint256, uint256, uint256) {
181
        if (_liqState.recoveryModeAtStart) {
182
            LiquidationRecoveryModeLocals
183
                memory _outputState = _liquidateIndividualCdpSetupCDPInRecoveryMode(_recoveryState);
184

                            
                        
185
            // housekeeping leftover collateral for liquidated CDP
186
            if (_outputState.totalSurplusCollShares > 0) {
187
                activePool.transferSystemCollShares(
188
                    address(collSurplusPool),
189
                    _outputState.totalSurplusCollShares
190
                );
191
            }
192

                            
                        
193
            return (
194
                _outputState.totalDebtToBurn,
195
                _outputState.totalCollSharesToSend,
196
                _outputState.totalDebtToRedistribute,
197
                _outputState.totalLiquidatorRewardCollShares,
198
                _outputState.totalSurplusCollShares
199
            );
200
        } else {
201
            LiquidationLocals memory _outputState = _liquidateIndividualCdpSetupCDPInNormalMode(
202
                _liqState
203
            );
204
            return (
205
                _outputState.totalDebtToBurn,
206
                _outputState.totalCollSharesToSend,
207
                _outputState.totalDebtToRedistribute,
208
                _outputState.totalLiquidatorRewardCollShares,
209
                _outputState.totalSurplusCollShares
210
            );
211
        }
212
    }
213

                            
                        
214
    function _liquidateIndividualCdpSetupCDPInNormalMode(
215
        LiquidationLocals memory _liqState
216
    ) private returns (LiquidationLocals memory) {
217
        // liquidate entire debt
218
        (
219
            uint256 _totalDebtToBurn,
220
            uint256 _totalColToSend,
221
            uint256 _liquidatorReward
222
        ) = _closeCdpByLiquidation(_liqState.cdpId);
223
        uint256 _cappedColPortion;
224
        uint256 _collSurplus;
225
        uint256 _debtToRedistribute;
226
        address _borrower = sortedCdps.getOwnerAddress(_liqState.cdpId);
227

                            
                        
228
        // I don't see an issue emitting the CdpUpdated() event up here and avoiding this extra cache, any objections?
229
        emit CdpUpdated(
230
            _liqState.cdpId,
231
            _borrower,
232
            _totalDebtToBurn,
233
            _totalColToSend,
234
            0,
235
            0,
236
            0,
237
            CdpOperation.liquidateInNormalMode
238
        );
239

                            
                        
240
        {
241
            (_cappedColPortion, _collSurplus, _debtToRedistribute) = _calculateSurplusAndCap(
242
                _liqState.ICR,
243
                _liqState.price,
244
                _totalDebtToBurn,
245
                _totalColToSend,
246
                true
247
            );
248
            if (_collSurplus > 0) {
249
                // due to division precision loss, should be zero surplus in normal mode
250
                _cappedColPortion = _cappedColPortion + _collSurplus;
251
                _collSurplus = 0;
252
            }
253
            if (_debtToRedistribute > 0) {
254
                _totalDebtToBurn = _totalDebtToBurn - _debtToRedistribute;
255
            }
256
        }
257
        _liqState.totalDebtToBurn = _liqState.totalDebtToBurn + _totalDebtToBurn;
258
        _liqState.totalCollSharesToSend = _liqState.totalCollSharesToSend + _cappedColPortion;
259
        _liqState.totalDebtToRedistribute = _liqState.totalDebtToRedistribute + _debtToRedistribute;
260
        _liqState.totalLiquidatorRewardCollShares =
261
            _liqState.totalLiquidatorRewardCollShares +
262
            _liquidatorReward;
263

                            
                        
264
        // Emit events
265
        uint _debtToColl = (_totalDebtToBurn * DECIMAL_PRECISION) / _liqState.price;
266
        uint _cappedColl = collateral.getPooledEthByShares(_cappedColPortion + _liquidatorReward);
267

                            
                        
268
        emit CdpLiquidated(
269
            _liqState.cdpId,
270
            _borrower,
271
            _totalDebtToBurn,
272
            // please note this is the collateral share of the liquidated CDP
273
            _cappedColPortion,
274
            CdpOperation.liquidateInNormalMode,
275
            msg.sender,
276
            _cappedColl > _debtToColl ? (_cappedColl - _debtToColl) : 0
277
        );
278

                            
                        
279
        return _liqState;
280
    }
281

                            
                        
282
    function _liquidateIndividualCdpSetupCDPInRecoveryMode(
283
        LiquidationRecoveryModeLocals memory _recoveryState
284
    ) private returns (LiquidationRecoveryModeLocals memory) {
285
        // liquidate entire debt
286
        (
287
            uint256 _totalDebtToBurn,
288
            uint256 _totalColToSend,
289
            uint256 _liquidatorReward
290
        ) = _closeCdpByLiquidation(_recoveryState.cdpId);
291

                            
                        
292
        // cap the liquidated collateral if required
293
        uint256 _cappedColPortion;
294
        uint256 _collSurplus;
295
        uint256 _debtToRedistribute;
296
        address _borrower = sortedCdps.getOwnerAddress(_recoveryState.cdpId);
297

                            
                        
298
        // I don't see an issue emitting the CdpUpdated() event up here and avoiding an extra cache of the values, any objections?
299
        emit CdpUpdated(
300
            _recoveryState.cdpId,
301
            _borrower,
302
            _totalDebtToBurn,
303
            _totalColToSend,
304
            0,
305
            0,
306
            0,
307
            CdpOperation.liquidateInRecoveryMode
308
        );
309

                            
                        
310
        // avoid stack too deep
311
        {
312
            (_cappedColPortion, _collSurplus, _debtToRedistribute) = _calculateSurplusAndCap(
313
                _recoveryState.ICR,
314
                _recoveryState.price,
315
                _totalDebtToBurn,
316
                _totalColToSend,
317
                true
318
            );
319
            if (_collSurplus > 0) {
320
                collSurplusPool.increaseSurplusCollShares(_borrower, _collSurplus);
321
                _recoveryState.totalSurplusCollShares =
322
                    _recoveryState.totalSurplusCollShares +
323
                    _collSurplus;
324
            }
325
            if (_debtToRedistribute > 0) {
326
                _totalDebtToBurn = _totalDebtToBurn - _debtToRedistribute;
327
            }
328
        }
329
        _recoveryState.totalDebtToBurn = _recoveryState.totalDebtToBurn + _totalDebtToBurn;
330
        _recoveryState.totalCollSharesToSend =
331
            _recoveryState.totalCollSharesToSend +
332
            _cappedColPortion;
333
        _recoveryState.totalDebtToRedistribute =
334
            _recoveryState.totalDebtToRedistribute +
335
            _debtToRedistribute;
336
        _recoveryState.totalLiquidatorRewardCollShares =
337
            _recoveryState.totalLiquidatorRewardCollShares +
338
            _liquidatorReward;
339

                            
                        
340
        // check if system back to normal mode
341
        _recoveryState.entireSystemDebt = _recoveryState.entireSystemDebt > _totalDebtToBurn
342
            ? _recoveryState.entireSystemDebt - _totalDebtToBurn
343
            : 0;
344
        _recoveryState.entireSystemColl = _recoveryState.entireSystemColl > _totalColToSend
345
            ? _recoveryState.entireSystemColl - _totalColToSend
346
            : 0;
347

                            
                        
348
        uint _debtToColl = (_totalDebtToBurn * DECIMAL_PRECISION) / _recoveryState.price;
349
        uint _cappedColl = collateral.getPooledEthByShares(_cappedColPortion + _liquidatorReward);
350
        emit CdpLiquidated(
351
            _recoveryState.cdpId,
352
            _borrower,
353
            _totalDebtToBurn,
354
            // please note this is the collateral share of the liquidated CDP
355
            _cappedColPortion,
356
            CdpOperation.liquidateInRecoveryMode,
357
            msg.sender,
358
            _cappedColl > _debtToColl ? (_cappedColl - _debtToColl) : 0
359
        );
360

                            
                        
361
        return _recoveryState;
362
    }
363

                            
                        
364
    // liquidate (and close) the CDP from an external liquidator
365
    // this function would return the liquidated debt and collateral of the given CDP
366
    // without emmiting events
367
    function _closeCdpByLiquidation(bytes32 _cdpId) private returns (uint256, uint256, uint256) {
368
        // calculate entire debt to repay
369
        (uint256 entireDebt, uint256 entireColl, ) = getDebtAndCollShares(_cdpId);
370

                            
                        
371
        // housekeeping after liquidation by closing the CDP
372
        _removeStake(_cdpId);
373
        uint256 _liquidatorReward = Cdps[_cdpId].liquidatorRewardShares;
374
        _closeCdp(_cdpId, Status.closedByLiquidation);
375

                            
                        
376
        return (entireDebt, entireColl, _liquidatorReward);
377
    }
378

                            
                        
379
    // Liquidate partially the CDP by an external liquidator
380
    // This function would return the liquidated debt and collateral of the given CDP
381
    function _liquidateCDPPartially(
382
        LiquidationLocals memory _partialState
383
    ) private returns (uint256, uint256) {
384
        bytes32 _cdpId = _partialState.cdpId;
385
        uint256 _partialDebt = _partialState.partialAmount;
386

                            
                        
387
        // calculate entire debt to repay
388
        CdpDebtAndCollShares memory _debtAndColl = _getDebtAndCollShares(_cdpId);
389
        _requirePartialLiqDebtSize(_partialDebt, _debtAndColl.entireDebt, _partialState.price);
390
        uint256 newDebt = _debtAndColl.entireDebt - _partialDebt;
391

                            
                        
392
        // credit to https://arxiv.org/pdf/2212.07306.pdf for details
393
        (uint256 _partialColl, uint256 newColl, ) = _calculateSurplusAndCap(
394
            _partialState.ICR,
395
            _partialState.price,
396
            _partialDebt,
397
            _debtAndColl.entireColl,
398
            false
399
        );
400

                            
                        
401
        // early return: if new collateral is zero, we have a full liqudiation
402
        if (newColl == 0) {
403
            return (0, 0);
404
        }
405

                            
                        
406
        // If we have coll remaining, it must meet minimum CDP size requirements
407
        _requirePartialLiqCollSize(collateral.getPooledEthByShares(newColl));
408

                            
                        
409
        // updating the CDP accounting for partial liquidation
410
        _partiallyReduceCdpDebt(_cdpId, _partialDebt, _partialColl);
411

                            
                        
412
        // reInsert into sorted CDP list after partial liquidation
413
        {
414
            _reInsertPartialLiquidation(
415
                _partialState,
416
                EbtcMath._computeNominalCR(newColl, newDebt),
417
                _debtAndColl.entireDebt,
418
                _debtAndColl.entireColl
419
            );
420
            uint _debtToColl = (_partialDebt * DECIMAL_PRECISION) / _partialState.price;
421
            uint _cappedColl = collateral.getPooledEthByShares(_partialColl);
422
            emit CdpPartiallyLiquidated(
423
                _cdpId,
424
                sortedCdps.getOwnerAddress(_cdpId),
425
                _partialDebt,
426
                _partialColl,
427
                CdpOperation.partiallyLiquidate,
428
                msg.sender,
429
                _cappedColl > _debtToColl ? (_cappedColl - _debtToColl) : 0
430
            );
431
        }
432
        return (_partialDebt, _partialColl);
433
    }
434

                            
                        
435
    function _partiallyReduceCdpDebt(
436
        bytes32 _cdpId,
437
        uint256 _partialDebt,
438
        uint256 _partialColl
439
    ) internal {
440
        Cdp storage _cdp = Cdps[_cdpId];
441

                            
                        
442
        uint256 _coll = _cdp.coll;
443
        uint256 _debt = _cdp.debt;
444

                            
                        
445
        _cdp.coll = _coll - _partialColl;
446
        _cdp.debt = _debt - _partialDebt;
447
        _updateStakeAndTotalStakes(_cdpId);
448

                            
                        
449
        _updateRedistributedDebtSnapshot(_cdpId);
450
    }
451

                            
                        
452
    // Re-Insertion into SortedCdp list after partial liquidation
453
    function _reInsertPartialLiquidation(
454
        LiquidationLocals memory _partialState,
455
        uint256 _newNICR,
456
        uint256 _oldDebt,
457
        uint256 _oldColl
458
    ) internal {
459
        bytes32 _cdpId = _partialState.cdpId;
460

                            
                        
461
        // ensure new ICR does NOT decrease due to partial liquidation
462
        // if original ICR is above LICR
463
        if (_partialState.ICR > LICR) {
464
            require(
465
                getICR(_cdpId, _partialState.price) >= _partialState.ICR,
466
                "LiquidationLibrary: !_newICR>=_ICR"
467
            );
468
        }
469

                            
                        
470
        // reInsert into sorted CDP list
471
        sortedCdps.reInsert(
472
            _cdpId,
473
            _newNICR,
474
            _partialState.upperPartialHint,
475
            _partialState.lowerPartialHint
476
        );
477
        emit CdpUpdated(
478
            _cdpId,
479
            sortedCdps.getOwnerAddress(_cdpId),
480
            _oldDebt,
481
            _oldColl,
482
            Cdps[_cdpId].debt,
483
            Cdps[_cdpId].coll,
484
            Cdps[_cdpId].stake,
485
            CdpOperation.partiallyLiquidate
486
        );
487
    }
488

                            
                        
489
    function _finalizeLiquidation(
490
        uint256 totalDebtToBurn,
491
        uint256 totalCollSharesToSend,
492
        uint256 totalDebtToRedistribute,
493
        uint256 totalLiquidatorRewardCollShares,
494
        uint256 totalSurplusCollShares,
495
        uint256 systemInitialCollShares,
496
        uint256 systemInitialDebt,
497
        uint256 price
498
    ) internal {
499
        // update the staking and collateral snapshots
500
        _updateSystemSnapshotsExcludeCollRemainder(totalCollSharesToSend);
501

                            
                        
502
        emit Liquidation(totalDebtToBurn, totalCollSharesToSend, totalLiquidatorRewardCollShares);
503

                            
                        
504
        _syncGracePeriodForGivenValues(
505
            systemInitialCollShares - totalCollSharesToSend - totalSurplusCollShares,
506
            systemInitialDebt - totalDebtToBurn,
507
            price
508
        );
509

                            
                        
510
        // redistribute debt if any
511
        if (totalDebtToRedistribute > 0) {
512
            _redistributeDebt(totalDebtToRedistribute);
513
        }
514

                            
                        
515
        // burn the debt from liquidator
516
        ebtcToken.burn(msg.sender, totalDebtToBurn);
517

                            
                        
518
        // offset debt from Active Pool
519
        activePool.decreaseSystemDebt(totalDebtToBurn);
520

                            
                        
521
        // CEI: ensure sending back collateral to liquidator is last thing to do
522
        activePool.transferSystemCollSharesAndLiquidatorReward(
523
            msg.sender,
524
            totalCollSharesToSend,
525
            totalLiquidatorRewardCollShares
526
        );
527
    }
528

                            
                        
529
    // Function that calculates the amount of collateral to send to liquidator (plus incentive) and the amount of collateral surplus
530
    function _calculateSurplusAndCap(
531
        uint256 _ICR,
532
        uint256 _price,
533
        uint256 _totalDebtToBurn,
534
        uint256 _totalColToSend,
535
        bool _fullLiquidation
536
    )
537
        private
538
        view
539
        returns (uint256 cappedColPortion, uint256 collSurplus, uint256 debtToRedistribute)
540
    {
541
        // Calculate liquidation incentive for liquidator:
542
        // If ICR is less than 103%: give away 103% worth of collateral to liquidator, i.e., repaidDebt * 103% / price
543
        // If ICR is more than 103%: give away min(ICR, 110%) worth of collateral to liquidator, i.e., repaidDebt * min(ICR, 110%) / price
544
        uint256 _incentiveColl;
545
        if (_ICR > LICR) {
546
            _incentiveColl = (_totalDebtToBurn * (_ICR > MCR ? MCR : _ICR)) / _price;
547
        } else {
548
            if (_fullLiquidation) {
549
                // for full liquidation, there would be some bad debt to redistribute
550
                _incentiveColl = collateral.getPooledEthByShares(_totalColToSend);
551
                uint256 _debtToRepay = (_incentiveColl * _price) / LICR;
552
                debtToRedistribute = _debtToRepay < _totalDebtToBurn
553
                    ? _totalDebtToBurn - _debtToRepay
554
                    : 0;
555
                // now CDP owner should have zero surplus to claim
556
                cappedColPortion = _totalColToSend;
557
            } else {
558
                // for partial liquidation, new ICR would deteriorate
559
                // since we give more incentive (103%) than current _ICR allowed
560
                _incentiveColl = (_totalDebtToBurn * LICR) / _price;
561
            }
562
        }
563
        if (cappedColPortion == 0) {
564
            cappedColPortion = collateral.getSharesByPooledEth(_incentiveColl);
565
        }
566
        cappedColPortion = cappedColPortion < _totalColToSend ? cappedColPortion : _totalColToSend;
567
        collSurplus = (cappedColPortion == _totalColToSend) ? 0 : _totalColToSend - cappedColPortion;
568
    }
569

                            
                        
570
    // --- Batch liquidation functions ---
571

                            
                        
572
    function _getLiquidationValuesNormalMode(
573
        uint256 _price,
574
        uint256 _TCR,
575
        LocalVariables_LiquidationSequence memory vars,
576
        LiquidationValues memory singleLiquidation
577
    ) internal {
578
        LiquidationLocals memory _liqState = LiquidationLocals(
579
            vars.cdpId,
580
            0,
581
            _price,
582
            vars.ICR,
583
            vars.cdpId,
584
            vars.cdpId,
585
            (false),
586
            _TCR,
587
            0,
588
            0,
589
            0,
590
            0,
591
            0
592
        );
593

                            
                        
594
        LiquidationLocals memory _outputState = _liquidateIndividualCdpSetupCDPInNormalMode(
595
            _liqState
596
        );
597

                            
                        
598
        singleLiquidation.entireCdpDebt = _outputState.totalDebtToBurn;
599
        singleLiquidation.debtToBurn = _outputState.totalDebtToBurn;
600
        singleLiquidation.totalCollToSendToLiquidator = _outputState.totalCollSharesToSend;
601
        singleLiquidation.collSurplus = _outputState.totalSurplusCollShares;
602
        singleLiquidation.debtToRedistribute = _outputState.totalDebtToRedistribute;
603
        singleLiquidation.liquidatorCollSharesReward = _outputState.totalLiquidatorRewardCollShares;
604
    }
605

                            
                        
606
    function _getLiquidationValuesRecoveryMode(
607
        uint256 _price,
608
        uint256 _systemDebt,
609
        uint256 _systemCollShares,
610
        LocalVariables_LiquidationSequence memory vars,
611
        LiquidationValues memory singleLiquidation
612
    ) internal {
613
        LiquidationRecoveryModeLocals memory _recState = LiquidationRecoveryModeLocals(
614
            _systemDebt,
615
            _systemCollShares,
616
            0,
617
            0,
618
            0,
619
            vars.cdpId,
620
            _price,
621
            vars.ICR,
622
            0,
623
            0
624
        );
625

                            
                        
626
        LiquidationRecoveryModeLocals
627
            memory _outputState = _liquidateIndividualCdpSetupCDPInRecoveryMode(_recState);
628

                            
                        
629
        singleLiquidation.entireCdpDebt = _outputState.totalDebtToBurn;
630
        singleLiquidation.debtToBurn = _outputState.totalDebtToBurn;
631
        singleLiquidation.totalCollToSendToLiquidator = _outputState.totalCollSharesToSend;
632
        singleLiquidation.collSurplus = _outputState.totalSurplusCollShares;
633
        singleLiquidation.debtToRedistribute = _outputState.totalDebtToRedistribute;
634
        singleLiquidation.liquidatorCollSharesReward = _outputState.totalLiquidatorRewardCollShares;
635
    }
636

                            
                        
637
    /*
638
     * Attempt to liquidate a custom list of cdps provided by the caller.
639

                            
                        
640
     callable by anyone, accepts a custom list of Cdps addresses as an argument. Steps through the provided list and attempts to liquidate every Cdp, until it reaches the end or it runs out of gas. A Cdp is liquidated only if it meets the conditions for liquidation. For a batch of 10 Cdps, the gas costs per liquidated Cdp are roughly between 75K-83K, for a batch of 50 Cdps between 54K-69K.
641
     */
642
    function batchLiquidateCdps(bytes32[] memory _cdpArray) external nonReentrantSelfAndBOps {
643
        require(
644
            _cdpArray.length != 0,
645
            "LiquidationLibrary: Calldata address array must not be empty"
646
        );
647

                            
                        
648
        LocalVariables_OuterLiquidationFunction memory vars;
649
        LiquidationTotals memory totals;
650

                            
                        
651
        // taking fee to avoid accounted for the calculation of the TCR
652
        _syncGlobalAccounting();
653

                            
                        
654
        vars.price = priceFeed.fetchPrice();
655
        (uint256 _TCR, uint256 systemColl, uint256 systemDebt) = _getTCRWithSystemDebtAndCollShares(
656
            vars.price
657
        );
658
        vars.recoveryModeAtStart = _TCR < CCR ? true : false;
659

                            
                        
660
        // Perform the appropriate batch liquidation - tally values and obtain their totals.
661
        if (vars.recoveryModeAtStart) {
662
            totals = _getTotalFromBatchLiquidate_RecoveryMode(
663
                vars.price,
664
                systemColl,
665
                systemDebt,
666
                _cdpArray
667
            );
668
        } else {
669
            //  if !vars.recoveryModeAtStart
670
            totals = _getTotalsFromBatchLiquidate_NormalMode(vars.price, _TCR, _cdpArray);
671
        }
672

                            
                        
673
        require(totals.totalDebtInSequence > 0, "LiquidationLibrary: nothing to liquidate");
674

                            
                        
675
        // housekeeping leftover collateral for liquidated CDPs
676
        if (totals.totalCollSurplus > 0) {
677
            activePool.transferSystemCollShares(address(collSurplusPool), totals.totalCollSurplus);
678
        }
679

                            
                        
680
        _finalizeLiquidation(
681
            totals.totalDebtToBurn,
682
            totals.totalCollToSendToLiquidator,
683
            totals.totalDebtToRedistribute,
684
            totals.totalCollReward,
685
            totals.totalCollSurplus,
686
            systemColl,
687
            systemDebt,
688
            vars.price
689
        );
690
    }
691

                            
                        
692
    /*
693
     * This function is used when the batch liquidation starts during Recovery Mode. However, it
694
     * handle the case where the system *leaves* Recovery Mode, part way through the liquidation processing
695
     */
696
    function _getTotalFromBatchLiquidate_RecoveryMode(
697
        uint256 _price,
698
        uint256 _systemCollShares,
699
        uint256 _systemDebt,
700
        bytes32[] memory _cdpArray
701
    ) internal returns (LiquidationTotals memory totals) {
702
        LocalVariables_LiquidationSequence memory vars;
703
        LiquidationValues memory singleLiquidation;
704

                            
                        
705
        vars.backToNormalMode = false;
706
        vars.entireSystemDebt = _systemDebt;
707
        vars.entireSystemColl = _systemCollShares;
708
        uint256 _TCR = _computeTCRWithGivenSystemValues(
709
            vars.entireSystemColl,
710
            vars.entireSystemDebt,
711
            _price
712
        );
713
        uint256 _cnt = _cdpArray.length;
714
        bool[] memory _liqFlags = new bool[](_cnt);
715
        uint256 _start;
716
        for (vars.i = _start; ; ) {
717
            vars.cdpId = _cdpArray[vars.i];
718
            // only for active cdps
719
            if (vars.cdpId != bytes32(0) && Cdps[vars.cdpId].status == Status.active) {
720
                vars.ICR = getSyncedICR(vars.cdpId, _price);
721

                            
                        
722
                if (
723
                    !vars.backToNormalMode &&
724
                    (_checkICRAgainstMCR(vars.ICR) || canLiquidateRecoveryMode(vars.ICR, _TCR))
725
                ) {
726
                    vars.price = _price;
727
                    _syncAccounting(vars.cdpId);
728
                    _getLiquidationValuesRecoveryMode(
729
                        _price,
730
                        vars.entireSystemDebt,
731
                        vars.entireSystemColl,
732
                        vars,
733
                        singleLiquidation
734
                    );
735

                            
                        
736
                    // Update aggregate trackers
737
                    vars.entireSystemDebt = vars.entireSystemDebt - singleLiquidation.debtToBurn;
738
                    vars.entireSystemColl =
739
                        vars.entireSystemColl -
740
                        singleLiquidation.totalCollToSendToLiquidator -
741
                        singleLiquidation.collSurplus;
742

                            
                        
743
                    // Add liquidation values to their respective running totals
744
                    totals = _addLiquidationValuesToTotals(totals, singleLiquidation);
745

                            
                        
746
                    _TCR = _computeTCRWithGivenSystemValues(
747
                        vars.entireSystemColl,
748
                        vars.entireSystemDebt,
749
                        _price
750
                    );
751
                    vars.backToNormalMode = _TCR < CCR ? false : true;
752
                    _liqFlags[vars.i] = true;
753
                } else if (vars.backToNormalMode && _checkICRAgainstMCR(vars.ICR)) {
754
                    _syncAccounting(vars.cdpId);
755
                    _getLiquidationValuesNormalMode(_price, _TCR, vars, singleLiquidation);
756

                            
                        
757
                    // Add liquidation values to their respective running totals
758
                    totals = _addLiquidationValuesToTotals(totals, singleLiquidation);
759
                    _liqFlags[vars.i] = true;
760
                }
761
                // In Normal Mode skip cdps with ICR >= MCR
762
            }
763
            ++vars.i;
764
            if (vars.i == _cnt) {
765
                break;
766
            }
767
        }
768
    }
769

                            
                        
770
    function _getTotalsFromBatchLiquidate_NormalMode(
771
        uint256 _price,
772
        uint256 _TCR,
773
        bytes32[] memory _cdpArray
774
    ) internal returns (LiquidationTotals memory totals) {
775
        LocalVariables_LiquidationSequence memory vars;
776
        LiquidationValues memory singleLiquidation;
777
        uint256 _cnt = _cdpArray.length;
778
        uint256 _start;
779
        for (vars.i = _start; ; ) {
780
            vars.cdpId = _cdpArray[vars.i];
781
            // only for active cdps
782
            if (vars.cdpId != bytes32(0) && Cdps[vars.cdpId].status == Status.active) {
783
                vars.ICR = getSyncedICR(vars.cdpId, _price);
784

                            
                        
785
                if (_checkICRAgainstMCR(vars.ICR)) {
786
                    _syncAccounting(vars.cdpId);
787
                    _getLiquidationValuesNormalMode(_price, _TCR, vars, singleLiquidation);
788

                            
                        
789
                    // Add liquidation values to their respective running totals
790
                    totals = _addLiquidationValuesToTotals(totals, singleLiquidation);
791
                }
792
            }
793
            ++vars.i;
794
            if (vars.i == _cnt) {
795
                break;
796
            }
797
        }
798
    }
799

                            
                        
800
    // --- Liquidation helper functions ---
801

                            
                        
802
    function _addLiquidationValuesToTotals(
803
        LiquidationTotals memory oldTotals,
804
        LiquidationValues memory singleLiquidation
805
    ) internal pure returns (LiquidationTotals memory newTotals) {
806
        // Tally all the values with their respective running totals
807
        newTotals.totalDebtInSequence =
808
            oldTotals.totalDebtInSequence +
809
            singleLiquidation.entireCdpDebt;
810
        newTotals.totalDebtToBurn = oldTotals.totalDebtToBurn + singleLiquidation.debtToBurn;
811
        newTotals.totalCollToSendToLiquidator =
812
            oldTotals.totalCollToSendToLiquidator +
813
            singleLiquidation.totalCollToSendToLiquidator;
814
        newTotals.totalDebtToRedistribute =
815
            oldTotals.totalDebtToRedistribute +
816
            singleLiquidation.debtToRedistribute;
817
        newTotals.totalCollSurplus = oldTotals.totalCollSurplus + singleLiquidation.collSurplus;
818
        newTotals.totalCollReward =
819
            oldTotals.totalCollReward +
820
            singleLiquidation.liquidatorCollSharesReward;
821

                            
                        
822
        return newTotals;
823
    }
824

                            
                        
825
    function _redistributeDebt(uint256 _debt) internal {
826
        if (_debt == 0) {
827
            return;
828
        }
829

                            
                        
830
        /*
831
         * Add distributed debt rewards-per-unit-staked to the running totals. Division uses a "feedback"
832
         * error correction, to keep the cumulative error low in the running totals systemDebtRedistributionIndex:
833
         *
834
         * 1) Form numerators which compensate for the floor division errors that occurred the last time this
835
         * function was called.
836
         * 2) Calculate "per-unit-staked" ratios.
837
         * 3) Multiply each ratio back by its denominator, to reveal the current floor division error.
838
         * 4) Store these errors for use in the next correction when this function is called.
839
         * 5) Note: static analysis tools complain about this "division before multiplication", however, it is intended.
840
         */
841
        uint256 EBTCDebtNumerator = (_debt * DECIMAL_PRECISION) + lastEBTCDebtErrorRedistribution;
842

                            
                        
843
        // Get the per-unit-staked terms
844
        uint256 _totalStakes = totalStakes;
845
        uint256 EBTCDebtRewardPerUnitStaked = EBTCDebtNumerator / _totalStakes;
846

                            
                        
847
        lastEBTCDebtErrorRedistribution =
848
            EBTCDebtNumerator -
849
            (EBTCDebtRewardPerUnitStaked * _totalStakes);
850

                            
                        
851
        // Add per-unit-staked terms to the running totals
852
        systemDebtRedistributionIndex = systemDebtRedistributionIndex + EBTCDebtRewardPerUnitStaked;
853

                            
                        
854
        emit SystemDebtRedistributionIndexUpdated(systemDebtRedistributionIndex);
855
    }
856

                            
                        
857
    // --- 'require' wrapper functions ---
858

                            
                        
859
    function _requirePartialLiqDebtSize(
860
        uint256 _partialDebt,
861
        uint256 _entireDebt,
862
        uint256 _price
863
    ) internal view {
864
        require(
865
            (_partialDebt + _convertDebtDenominationToBtc(MIN_NET_COLL, _price)) <= _entireDebt,
866
            "LiquidationLibrary: Partial debt liquidated must be less than total debt"
867
        );
868
    }
869

                            
                        
870
    function _requirePartialLiqCollSize(uint256 _entireColl) internal pure {
871
        require(
872
            _entireColl >= MIN_NET_COLL,
873
            "LiquidationLibrary: Coll remaining in partially liquidated CDP must be >= minimum"
874
        );
875
    }
876
}
877

                            
                        

Lines covered: 28 / 39 (71.8%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
import "./Interfaces/IPriceFeed.sol";
6
import "./Interfaces/ICdpManager.sol";
7
import "./Interfaces/ISortedCdps.sol";
8
import "./Interfaces/ICdpManagerData.sol";
9
import "./Dependencies/EbtcBase.sol";
10

                            
                        
11
/// @notice Helper to turn a sequence into CDP id array for batch liquidation
12
/// @dev Note this sequencer only serves as an approximation tool to provide "best-effort"
13
/// @dev that return a list of CDP ids which could be consumed by "CdpManager.batchLiquidateCdps()".
14
/// @dev It is possible that some of the returned CDPs might be skipped (not liquidatable any more)
15
/// @dev during liquidation execution due to change of the system states
16
/// @dev e.g., TCR brought back from Recovery Mode to Normal Mode
17
contract LiquidationSequencer is EbtcBase {
18
    ICdpManager public immutable cdpManager;
19
    ISortedCdps public immutable sortedCdps;
20

                            
                        
21
    constructor(
22
        address _cdpManagerAddress,
23
        address _sortedCdpsAddress,
24
        address _priceFeedAddress,
25
        address _activePoolAddress,
26
        address _collateralAddress
27
    ) EbtcBase(_activePoolAddress, _priceFeedAddress, _collateralAddress) {
28
        cdpManager = ICdpManager(_cdpManagerAddress);
29
        sortedCdps = ISortedCdps(_sortedCdpsAddress);
30
    }
31

                            
                        
32
    /// @dev Get first N batch of liquidatable Cdps at current price
33
    /// @dev Non-view function that updates and returns live price at execution time
34
    /// @dev could use callStatic offline to save gas
35
    function sequenceLiqToBatchLiq(uint256 _n) external returns (bytes32[] memory _array) {
36
        uint256 _price = priceFeed.fetchPrice();
37
        return sequenceLiqToBatchLiqWithPrice(_n, _price);
38
    }
39

                            
                        
40
    /// @dev Get first N batch of liquidatable Cdps at specified price
41
    /// @dev Non-view function that will sync global state
42
    /// @dev could use callStatic offline to save gas
43
    function sequenceLiqToBatchLiqWithPrice(
44
        uint256 _n,
45
        uint256 _price
46
    ) public returns (bytes32[] memory _array) {
47
        cdpManager.syncGlobalAccountingAndGracePeriod();
48
        (uint256 _TCR, , ) = _getTCRWithSystemDebtAndCollShares(_price);
49
        return _sequenceLiqToBatchLiq(_n, _price, _TCR);
50
    }
51

                            
                        
52
    // return CdpId array (in NICR-decreasing order same as SortedCdps)
53
    // including the last N CDPs in sortedCdps for batch liquidation
54
    function _sequenceLiqToBatchLiq(
55
        uint256 _n,
56
        uint256 _price,
57
        uint256 _TCR
58
    ) internal view returns (bytes32[] memory _array) {
59
        if (_n > 0) {
60
            // get count of liquidatable CDPs with 1st iteration
61
            (uint256 _cnt, ) = _iterateOverSortedCdps(0, _TCR, _n, _price);
62

                            
                        
63
            // retrieve liquidatable CDPs with 2nd iteration
64
            (uint256 _j, bytes32[] memory _returnedArray) = _iterateOverSortedCdps(
65
                _cnt,
66
                _TCR,
67
                _n,
68
                _price
69
            );
70
            require(_j == _cnt, "LiquidationSequencer: wrong sequence conversion!");
71
            _array = _returnedArray;
72
        }
73
    }
74

                            
                        
75
    function _iterateOverSortedCdps(
76
        uint256 _realCount,
77
        uint256 _TCR,
78
        uint256 _n,
79
        uint256 _price
80
    ) internal view returns (uint256 _cnt, bytes32[] memory _array) {
81
        // if there is already a count (calculated from previous iteration)
82
        // we use the value to initialize CDP id array for return
83
        if (_realCount > 0) {
84
            _array = new bytes32[](_realCount);
85
        }
86

                            
                        
87
        // initialize variables for this iteration
88
        bytes32 _last = sortedCdps.getLast();
89
        bytes32 _first = sortedCdps.getFirst();
90
        bytes32 _cdpId = _last;
91

                            
                        
92
        for (uint256 i = 0; i < (_realCount > 0 ? _realCount : _n) && _cdpId != _first; ) {
93
            bool _liquidatable = _checkICRAgainstLiqThreshold(
94
                cdpManager.getSyncedICR(_cdpId, _price),
95
                _TCR
96
            );
97
            if (_liquidatable) {
98
                if (_realCount > 0) {
99
                    _array[_realCount - _cnt - 1] = _cdpId;
100
                }
101
                unchecked {
102
                    ++_cnt;
103
                }
104
                _cdpId = sortedCdps.getPrev(_cdpId);
105
            } else {
106
                // breaking loop early if not liquidatable due to sorted (descending) list of CDPs
107
                break;
108
            }
109
            unchecked {
110
                ++i;
111
            }
112
        }
113
    }
114
}
115

                            
                        

Lines covered: 141 / 226 (62.4%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
import "./Interfaces/ISortedCdps.sol";
6
import "./Interfaces/ICdpManager.sol";
7
import "./Interfaces/IBorrowerOperations.sol";
8

                            
                        
9
/*
10
 * A sorted doubly linked list with nodes sorted in descending order.
11
 *
12
 * Nodes map to active Cdps in the system by ID.
13
 * Nodes are ordered according to their current nominal individual collateral ratio (NICR),
14
 * which is like the ICR but without the price, i.e., just collateral / debt.
15
 *
16
 * The list optionally accepts insert position hints.
17
 *
18
 * NICRs are computed dynamically at runtime, and not stored on the Node. This is because NICRs of active Cdps
19
 * change dynamically as liquidation events occur.
20
 *
21
 * The list relies on the fact that liquidation events preserve ordering: a liquidation decreases the NICRs of all active Cdps,
22
 * but maintains their order. A node inserted based on current NICR will maintain the correct position,
23
 * relative to it's peers, as rewards accumulate, as long as it's raw collateral and debt have not changed.
24
 * Thus, Nodes remain sorted by current NICR.
25
 *
26
 * Nodes need only be re-inserted upon a Cdp operation - when the owner adds or removes collateral or debt
27
 * to their position.
28
 *
29
 * The list is a modification of the following audited SortedDoublyLinkedList:
30
 * https://github.com/livepeer/protocol/blob/master/contracts/libraries/SortedDoublyLL.sol
31
 *
32
 *
33
 * Changes made in the Liquity implementation:
34
 *
35
 * - Keys have been removed from nodes
36
 *
37
 * - Ordering checks for insertion are performed by comparing an NICR argument to the current NICR, calculated at runtime.
38
 *   The list relies on the property that ordering by ICR is maintained as the stETH:BTC price varies.
39
 *
40
 * - Public functions with parameters have been made internal to save gas, and given an external wrapper function for external access
41
 */
42
contract SortedCdps is ISortedCdps {
43
    string public constant NAME = "SortedCdps";
44

                            
                        
45
    address public immutable borrowerOperationsAddress;
46

                            
                        
47
    ICdpManager public immutable cdpManager;
48

                            
                        
49
    uint256 public immutable maxSize;
50

                            
                        
51
    uint256 constant ADDRESS_SHIFT = 96; // 8 * 12; Puts the address at leftmost bytes32 position
52
    uint256 constant BLOCK_SHIFT = 64; // 8 * 8; Puts the block value after the address
53

                            
                        
54
    // Information for a node in the list
55
    struct Node {
56
        bytes32 nextId; // Id of next node (smaller NICR) in the list
57
        bytes32 prevId; // Id of previous node (larger NICR) in the list
58
    }
59

                            
                        
60
    // Information for the list
61
    struct Data {
62
        bytes32 head; // Head of the list. Also the node in the list with the largest NICR
63
        bytes32 tail; // Tail of the list. Also the node in the list with the smallest NICR
64
        uint256 size; // Current size of the list
65
        mapping(bytes32 => Node) nodes; // Track the corresponding ids for each node in the list
66
    }
67

                            
                        
68
    Data public data;
69

                            
                        
70
    uint256 public nextCdpNonce;
71
    bytes32 public constant dummyId =
72
        0x0000000000000000000000000000000000000000000000000000000000000000;
73

                            
                        
74
    // --- Dependency setters ---
75
    constructor(uint256 _size, address _cdpManagerAddress, address _borrowerOperationsAddress) {
76
        if (_size == 0) {
77
            _size = type(uint256).max;
78
        }
79

                            
                        
80
        maxSize = _size;
81

                            
                        
82
        cdpManager = ICdpManager(_cdpManagerAddress);
83
        borrowerOperationsAddress = _borrowerOperationsAddress;
84
    }
85

                            
                        
86
    // https://github.com/balancer-labs/balancer-v2-monorepo/blob/18bd5fb5d87b451cc27fbd30b276d1fb2987b529/pkg/vault/contracts/PoolRegistry.sol
87
    function toCdpId(
88
        address owner,
89
        uint256 blockHeight,
90
        uint256 nonce
91
    ) public pure returns (bytes32) {
92
        bytes32 serialized;
93

                            
                        
94
        serialized |= bytes32(nonce);
95
        serialized |= bytes32(blockHeight) << BLOCK_SHIFT; // to accommendate more than 4.2 billion blocks
96
        serialized |= bytes32(uint256(uint160(owner))) << ADDRESS_SHIFT;
97

                            
                        
98
        return serialized;
99
    }
100

                            
                        
101
    /// @notice Get owner address of a given Cdp, by CdpId.
102
    /// @dev The owner address is stored in the first 20 bytes of the CdpId
103
    /// @param cdpId cdpId of Cdp to get owner of
104
    /// @return owner address of the Cdp
105
    function getOwnerAddress(bytes32 cdpId) public pure override returns (address) {
106
        uint256 _tmp = uint256(cdpId) >> ADDRESS_SHIFT;
107
        return address(uint160(_tmp));
108
    }
109

                            
                        
110
    function nonExistId() public pure override returns (bytes32) {
111
        return dummyId;
112
    }
113

                            
                        
114
    /// @notice Find a specific Cdp for a given owner, indexed by it's place in the linked list relative to other Cdps owned by the same address
115
    /// @notice Reverts if the index exceeds the number of active Cdps owned by the given owner
116
    /// @dev Intended for off-chain use, O(n) operation on size of SortedCdps linked list
117
    /// @param owner address of Cdp owner
118
    /// @param index index of Cdp, ordered by position in linked list relative to Cdps of the same owner
119
    function cdpOfOwnerByIndex(
120
        address owner,
121
        uint256 index
122
    ) external view override returns (bytes32) {
123
        (bytes32 _cdpId, ) = _cdpOfOwnerByIndex(owner, index, dummyId, 0);
124
        return _cdpId;
125
    }
126

                            
                        
127
    /// @dev a pagination-flavor search (from least ICR to biggest ICR) for CDP owned by given owner and specified index (starting at given CDP)
128
    /// @param startNodeId the seach traversal will start at this given CDP instead of the tail of the list
129
    /// @param maxNodes the traversal will go through the list by this given maximum limit of number of CDPs
130
    function cdpOfOwnerByIdx(
131
        address owner,
132
        uint256 index,
133
        bytes32 startNodeId,
134
        uint maxNodes
135
    ) external view override returns (bytes32, bool) {
136
        return _cdpOfOwnerByIndex(owner, index, startNodeId, maxNodes);
137
    }
138

                            
                        
139
    /// @dev return EITHER the found CDP owned by given owner & index with a true indicator OR
140
    /// @dev        current lastly-visited CDP as the startNode for next pagination with a false indicator
141
    function _cdpOfOwnerByIndex(
142
        address owner,
143
        uint256 index,
144
        bytes32 startNodeId,
145
        uint maxNodes
146
    ) internal view returns (bytes32, bool) {
147
        // walk the list, until we get to the indexed CDP
148
        // start at the given node or from the tail of list
149
        bytes32 _currentCdpId = (startNodeId == dummyId ? data.tail : startNodeId);
150
        uint _currentIndex = 0;
151
        uint i;
152

                            
                        
153
        while (_currentCdpId != dummyId) {
154
            // if the current Cdp is owned by specified owner
155
            if (getOwnerAddress(_currentCdpId) == owner) {
156
                // if the current index of the owner Cdp matches specified index
157
                if (_currentIndex == index) {
158
                    return (_currentCdpId, true);
159
                } else {
160
                    // if not, increment the owner index as we've seen a Cdp owned by them
161
                    _currentIndex = _currentIndex + 1;
162
                }
163
            }
164
            ++i;
165

                            
                        
166
            // move to the next Cdp in the list
167
            _currentCdpId = data.nodes[_currentCdpId].prevId;
168

                            
                        
169
            // cut the run if we exceed expected iterations through the loop
170
            if (maxNodes > 0 && i >= maxNodes) {
171
                break;
172
            }
173
        }
174
        // if we reach maximum iteration or end of list
175
        // without seeing the specified index for the owner
176
        // then maybe a new pagination is needed
177
        return (_currentCdpId, false);
178
    }
179

                            
                        
180
    /// @notice Get active Cdp count of a given address
181
    /// @dev Intended for off-chain use, O(n) operation on size of linked list
182
    function cdpCountOf(address owner) external view override returns (uint256) {
183
        (uint256 _cnt, ) = _cdpCountOf(owner, dummyId, 0);
184
        return _cnt;
185
    }
186

                            
                        
187
    /// @dev a pagination-flavor search count of (from least ICR to biggest ICR) CDPs owned by given owner (starting at given CDP)
188
    /// @param startNodeId the count traversal will start at this given CDP instead of the tail of the list
189
    /// @param maxNodes the traversal will go through the list by this given maximum limit of number of CDPs
190
    function getCdpCountOf(
191
        address owner,
192
        bytes32 startNodeId,
193
        uint maxNodes
194
    ) external view override returns (uint256, bytes32) {
195
        return _cdpCountOf(owner, startNodeId, maxNodes);
196
    }
197

                            
                        
198
    /// @dev return the found CDP count owned by given owner with
199
    /// @dev        current lastly-visited CDP as the startNode for next pagination
200
    function _cdpCountOf(
201
        address owner,
202
        bytes32 startNodeId,
203
        uint maxNodes
204
    ) internal view returns (uint256, bytes32) {
205
        // walk the list, until we get to the count
206
        // start at the given node or from the tail of list
207
        bytes32 _currentCdpId = (startNodeId == dummyId ? data.tail : startNodeId);
208
        uint _ownedCount = 0;
209
        uint i = 0;
210

                            
                        
211
        while (_currentCdpId != dummyId) {
212
            // if the current Cdp is owned by specified owner
213
            if (getOwnerAddress(_currentCdpId) == owner) {
214
                _ownedCount = _ownedCount + 1;
215
            }
216
            ++i;
217

                            
                        
218
            // move to the next Cdp in the list
219
            _currentCdpId = data.nodes[_currentCdpId].prevId;
220

                            
                        
221
            // cut the run if we exceed expected iterations through the loop
222
            if (maxNodes > 0 && i >= maxNodes) {
223
                break;
224
            }
225
        }
226
        return (_ownedCount, _currentCdpId);
227
    }
228

                            
                        
229
    /// @notice Get all active Cdps for a given address
230
    /// @dev Intended for off-chain use, O(n) operation on size of linked list
231
    function getCdpsOf(address owner) external view override returns (bytes32[] memory cdps) {
232
        // Naive method uses two-pass strategy to determine exactly how many Cdps are owned by owner
233
        // This roughly halves the amount of Cdps we can process before relying on pagination or off-chain methods
234
        (uint _ownedCount, ) = _cdpCountOf(owner, dummyId, 0);
235
        if (_ownedCount > 0) {
236
            (bytes32[] memory _allCdps, , ) = _getCdpsOf(owner, dummyId, 0, _ownedCount);
237
            cdps = _allCdps;
238
        }
239
    }
240

                            
                        
241
    /// @dev a pagination-flavor search retrieval of (from least ICR to biggest ICR) CDPs owned by given owner (starting at given CDP)
242
    /// @param startNodeId the traversal will start at this given CDP instead of the tail of the list
243
    /// @param maxNodes the traversal will go through the list by this given maximum limit of number of CDPs
244
    function getAllCdpsOf(
245
        address owner,
246
        bytes32 startNodeId,
247
        uint maxNodes
248
    ) external view override returns (bytes32[] memory, uint256, bytes32) {
249
        // Naive method uses two-pass strategy to determine exactly how many Cdps are owned by owner
250
        // This roughly halves the amount of Cdps we can process before relying on pagination or off-chain methods
251
        (uint _ownedCount, ) = _cdpCountOf(owner, startNodeId, maxNodes);
252
        return _getCdpsOf(owner, startNodeId, maxNodes, _ownedCount);
253
    }
254

                            
                        
255
    /// @dev return EITHER the found CDPs (also the count) owned by given owner OR empty array with
256
    /// @dev        current lastly-visited CDP as the startNode for next pagination
257
    function _getCdpsOf(
258
        address owner,
259
        bytes32 startNodeId,
260
        uint maxNodes,
261
        uint maxArraySize
262
    ) internal view returns (bytes32[] memory, uint256, bytes32) {
263
        if (maxArraySize == 0) {
264
            return (new bytes32[](0), 0, dummyId);
265
        }
266

                            
                        
267
        // Two-pass strategy, halving the amount of Cdps we can process before relying on pagination or off-chain methods
268
        bytes32[] memory userCdps = new bytes32[](maxArraySize);
269
        uint i = 0;
270
        uint _cdpRetrieved;
271

                            
                        
272
        // walk the list, until we get to the index
273
        // start at the given node or from the tail of list
274
        bytes32 _currentCdpId = (startNodeId == dummyId ? data.tail : startNodeId);
275

                            
                        
276
        while (_currentCdpId != dummyId) {
277
            // if the current Cdp is owned by specified owner
278
            if (getOwnerAddress(_currentCdpId) == owner) {
279
                userCdps[_cdpRetrieved] = _currentCdpId;
280
                ++_cdpRetrieved;
281
            }
282
            ++i;
283

                            
                        
284
            // move to the next Cdp in the list
285
            _currentCdpId = data.nodes[_currentCdpId].prevId;
286

                            
                        
287
            // cut the run if we exceed expected iterations through the loop
288
            if (maxNodes > 0 && i >= maxNodes) {
289
                break;
290
            }
291
        }
292

                            
                        
293
        return (userCdps, _cdpRetrieved, _currentCdpId);
294
    }
295

                            
                        
296
    /*
297
     * @dev Add a node to the list
298
     * @param owner cdp owner
299
     * @param _NICR Node's NICR
300
     * @param _prevId Id of previous node for the insert position
301
     * @param _nextId Id of next node for the insert position
302
     */
303
    function insert(
304
        address owner,
305
        uint256 _NICR,
306
        bytes32 _prevId,
307
        bytes32 _nextId
308
    ) external override returns (bytes32) {
309
        _requireCallerIsBOorCdpM();
310
        bytes32 _id = toCdpId(owner, block.number, nextCdpNonce);
311
        require(cdpManager.getCdpStatus(_id) == 0, "SortedCdps: new id is NOT nonExistent!");
312

                            
                        
313
        _insert(_id, _NICR, _prevId, _nextId);
314

                            
                        
315
        unchecked {
316
            ++nextCdpNonce;
317
        }
318

                            
                        
319
        return _id;
320
    }
321

                            
                        
322
    function _insert(bytes32 _id, uint256 _NICR, bytes32 _prevId, bytes32 _nextId) internal {
323
        // List must not be full
324
        require(!isFull(), "SortedCdps: List is full");
325
        // List must not already contain node
326
        require(!contains(_id), "SortedCdps: List already contains the node");
327
        // Node id must not be null
328
        require(_id != dummyId, "SortedCdps: Id cannot be zero");
329
        // NICR must be non-zero
330
        require(_NICR > 0, "SortedCdps: NICR must be positive");
331

                            
                        
332
        bytes32 prevId = _prevId;
333
        bytes32 nextId = _nextId;
334

                            
                        
335
        if (!_validInsertPosition(_NICR, prevId, nextId)) {
336
            // Sender's hint was not a valid insert position
337
            // Use sender's hint to find a valid insert position
338
            (prevId, nextId) = _findInsertPosition(_NICR, prevId, nextId);
339
        }
340

                            
                        
341
        if (prevId == dummyId && nextId == dummyId) {
342
            // Insert as head and tail
343
            data.head = _id;
344
            data.tail = _id;
345
        } else if (prevId == dummyId) {
346
            // Insert before `prevId` as the head
347
            data.nodes[_id].nextId = data.head;
348
            data.nodes[data.head].prevId = _id;
349
            data.head = _id;
350
        } else if (nextId == dummyId) {
351
            // Insert after `nextId` as the tail
352
            data.nodes[_id].prevId = data.tail;
353
            data.nodes[data.tail].nextId = _id;
354
            data.tail = _id;
355
        } else {
356
            // Insert at insert position between `prevId` and `nextId`
357
            data.nodes[_id].nextId = nextId;
358
            data.nodes[_id].prevId = prevId;
359
            data.nodes[prevId].nextId = _id;
360
            data.nodes[nextId].prevId = _id;
361
        }
362

                            
                        
363
        data.size = data.size + 1;
364
        emit NodeAdded(_id, _NICR);
365
    }
366

                            
                        
367
    function remove(bytes32 _id) external override {
368
        _requireCallerIsCdpManager();
369
        _remove(_id);
370
    }
371

                            
                        
372
    function batchRemove(bytes32[] memory _ids) external override {
373
        _requireCallerIsCdpManager();
374
        uint256 _len = _ids.length;
375
        require(_len > 1, "SortedCdps: batchRemove() only apply to multiple cdpIds!");
376

                            
                        
377
        bytes32 _firstPrev = data.nodes[_ids[0]].prevId;
378
        bytes32 _lastNext = data.nodes[_ids[_len - 1]].nextId;
379

                            
                        
380
        require(
381
            _firstPrev != dummyId || _lastNext != dummyId,
382
            "SortedCdps: batchRemove() leave ZERO node left!"
383
        );
384

                            
                        
385
        for (uint256 i = 0; i < _len; ++i) {
386
            require(contains(_ids[i]), "SortedCdps: List does not contain the id");
387
        }
388

                            
                        
389
        // orphan nodes in between to save gas
390
        if (_firstPrev != dummyId) {
391
            data.nodes[_firstPrev].nextId = _lastNext;
392
        } else {
393
            data.head = _lastNext;
394
        }
395
        if (_lastNext != dummyId) {
396
            data.nodes[_lastNext].prevId = _firstPrev;
397
        } else {
398
            data.tail = _firstPrev;
399
        }
400

                            
                        
401
        // delete node & owner storages to get gas refund
402
        for (uint i = 0; i < _len; ++i) {
403
            delete data.nodes[_ids[i]];
404
            emit NodeRemoved(_ids[i]);
405
        }
406
        data.size = data.size - _len;
407
    }
408

                            
                        
409
    /*
410
     * @dev Remove a node from the list
411
     * @param _id Node's id
412
     */
413
    function _remove(bytes32 _id) internal {
414
        // List must contain the node
415
        require(contains(_id), "SortedCdps: List does not contain the id");
416

                            
                        
417
        if (data.size > 1) {
418
            // List contains more than a single node
419
            if (_id == data.head) {
420
                // The removed node is the head
421
                // Set head to next node
422
                data.head = data.nodes[_id].nextId;
423
                // Set prev pointer of new head to null
424
                data.nodes[data.head].prevId = dummyId;
425
            } else if (_id == data.tail) {
426
                // The removed node is the tail
427
                // Set tail to previous node
428
                data.tail = data.nodes[_id].prevId;
429
                // Set next pointer of new tail to null
430
                data.nodes[data.tail].nextId = dummyId;
431
            } else {
432
                // The removed node is neither the head nor the tail
433
                // Set next pointer of previous node to the next node
434
                data.nodes[data.nodes[_id].prevId].nextId = data.nodes[_id].nextId;
435
                // Set prev pointer of next node to the previous node
436
                data.nodes[data.nodes[_id].nextId].prevId = data.nodes[_id].prevId;
437
            }
438
        } else {
439
            // List contains a single node
440
            // Set the head and tail to null
441
            data.head = dummyId;
442
            data.tail = dummyId;
443
        }
444

                            
                        
445
        delete data.nodes[_id];
446
        data.size = data.size - 1;
447
        emit NodeRemoved(_id);
448
    }
449

                            
                        
450
    /*
451
     * @dev Re-insert the node at a new position, based on its new NICR
452
     * @param _id Node's id
453
     * @param _newNICR Node's new NICR
454
     * @param _prevId Id of previous node for the new insert position
455
     * @param _nextId Id of next node for the new insert position
456
     */
457
    function reInsert(
458
        bytes32 _id,
459
        uint256 _newNICR,
460
        bytes32 _prevId,
461
        bytes32 _nextId
462
    ) external override {
463
        _requireCallerIsBOorCdpM();
464
        // List must contain the node
465
        require(contains(_id), "SortedCdps: List does not contain the id");
466
        // NICR must be non-zero
467
        require(_newNICR > 0, "SortedCdps: NICR must be positive");
468

                            
                        
469
        // Remove node from the list
470
        _remove(_id);
471

                            
                        
472
        _insert(_id, _newNICR, _prevId, _nextId);
473
    }
474

                            
                        
475
    /**
476
     * @dev Checks if the list contains a given node
477
     * @param _id The ID of the node
478
     * @return true if the node exists, false otherwise
479
     */
480
    function contains(bytes32 _id) public view override returns (bool) {
481
        bool _exist = _id != dummyId && (data.head == _id || data.tail == _id);
482
        if (!_exist) {
483
            Node memory _node = data.nodes[_id];
484
            _exist = _id != dummyId && (_node.nextId != dummyId && _node.prevId != dummyId);
485
        }
486
        return _exist;
487
    }
488

                            
                        
489
    /**
490
     * @dev Checks if the list is full
491
     * @return true if the list is full, false otherwise
492
     */
493
    function isFull() public view override returns (bool) {
494
        return data.size == maxSize;
495
    }
496

                            
                        
497
    /**
498
     * @dev Checks if the list is empty
499
     * @return true if the list is empty, false otherwise
500
     */
501
    function isEmpty() public view override returns (bool) {
502
        return data.size == 0;
503
    }
504

                            
                        
505
    /**
506
     * @dev Returns the current size of the list
507
     * @return The current size of the list
508
     */
509
    function getSize() external view override returns (uint256) {
510
        return data.size;
511
    }
512

                            
                        
513
    /**
514
     * @dev Returns the maximum size of the list
515
     * @return The maximum size of the list
516
     */
517
    function getMaxSize() external view override returns (uint256) {
518
        return maxSize;
519
    }
520

                            
                        
521
    /**
522
     * @dev Returns the first node in the list (node with the largest NICR)
523
     * @return The ID of the first node
524
     */
525
    function getFirst() external view override returns (bytes32) {
526
        return data.head;
527
    }
528

                            
                        
529
    /**
530
     * @dev Returns the last node in the list (node with the smallest NICR)
531
     * @return The ID of the last node
532
     */
533
    function getLast() external view override returns (bytes32) {
534
        return data.tail;
535
    }
536

                            
                        
537
    /**
538
     * @dev Returns the next node (with a smaller NICR) in the list for a given node
539
     * @param _id The ID of the node
540
     * @return The ID of the next node
541
     */
542
    function getNext(bytes32 _id) external view override returns (bytes32) {
543
        return data.nodes[_id].nextId;
544
    }
545

                            
                        
546
    /**
547
     * @dev Returns the previous node (with a larger NICR) in the list for a given node
548
     * @param _id The ID of the node
549
     * @return The ID of the previous node
550
     */
551
    function getPrev(bytes32 _id) external view override returns (bytes32) {
552
        return data.nodes[_id].prevId;
553
    }
554

                            
                        
555
    /*
556
     * @dev Check if a pair of nodes is a valid insertion point for a new node with the given NICR
557
     * @param _NICR Node's NICR
558
     * @param _prevId Id of previous node for the insert position
559
     * @param _nextId Id of next node for the insert position
560
     * @return true if the position is valid, false otherwise
561
     */
562
    function validInsertPosition(
563
        uint256 _NICR,
564
        bytes32 _prevId,
565
        bytes32 _nextId
566
    ) external view override returns (bool) {
567
        return _validInsertPosition(_NICR, _prevId, _nextId);
568
    }
569

                            
                        
570
    function _validInsertPosition(
571
        uint256 _NICR,
572
        bytes32 _prevId,
573
        bytes32 _nextId
574
    ) internal view returns (bool) {
575
        if (_prevId == dummyId && _nextId == dummyId) {
576
            // `(null, null)` is a valid insert position if the list is empty
577
            return isEmpty();
578
        } else if (_prevId == dummyId) {
579
            // `(null, _nextId)` is a valid insert position if `_nextId` is the head of the list
580
            return data.head == _nextId && _NICR >= cdpManager.getNominalICR(_nextId);
581
        } else if (_nextId == dummyId) {
582
            // `(_prevId, null)` is a valid insert position if `_prevId` is the tail of the list
583
            return data.tail == _prevId && _NICR <= cdpManager.getNominalICR(_prevId);
584
        } else {
585
            // `(_prevId, _nextId)` is a valid insert position if they are adjacent nodes and `_NICR` falls between the two nodes' NICRs
586
            return
587
                data.nodes[_prevId].nextId == _nextId &&
588
                cdpManager.getNominalICR(_prevId) >= _NICR &&
589
                _NICR >= cdpManager.getNominalICR(_nextId);
590
        }
591
    }
592

                            
                        
593
    /*
594
     * @dev Descend the list (larger NICRs to smaller NICRs) to find a valid insert position
595
     * @param _NICR Node's NICR
596
     * @param _startId Id of node to start descending the list from
597
     */
598
    function _descendList(uint256 _NICR, bytes32 _startId) internal view returns (bytes32, bytes32) {
599
        // If `_startId` is the head, check if the insert position is before the head
600
        if (data.head == _startId && _NICR >= cdpManager.getNominalICR(_startId)) {
601
            return (dummyId, _startId);
602
        }
603

                            
                        
604
        bytes32 prevId = _startId;
605
        bytes32 nextId = data.nodes[prevId].nextId;
606

                            
                        
607
        // Descend the list until we reach the end or until we find a valid insert position
608
        while (prevId != dummyId && !_validInsertPosition(_NICR, prevId, nextId)) {
609
            prevId = data.nodes[prevId].nextId;
610
            nextId = data.nodes[prevId].nextId;
611
        }
612

                            
                        
613
        return (prevId, nextId);
614
    }
615

                            
                        
616
    /*
617
     * @dev Ascend the list (smaller NICRs to larger NICRs) to find a valid insert position
618
     * @param _NICR Node's NICR
619
     * @param _startId Id of node to start ascending the list from
620
     */
621
    function _ascendList(uint256 _NICR, bytes32 _startId) internal view returns (bytes32, bytes32) {
622
        // If `_startId` is the tail, check if the insert position is after the tail
623
        if (data.tail == _startId && _NICR <= cdpManager.getNominalICR(_startId)) {
624
            return (_startId, dummyId);
625
        }
626

                            
                        
627
        bytes32 nextId = _startId;
628
        bytes32 prevId = data.nodes[nextId].prevId;
629

                            
                        
630
        // Ascend the list until we reach the end or until we find a valid insertion point
631
        while (nextId != dummyId && !_validInsertPosition(_NICR, prevId, nextId)) {
632
            nextId = data.nodes[nextId].prevId;
633
            prevId = data.nodes[nextId].prevId;
634
        }
635

                            
                        
636
        return (prevId, nextId);
637
    }
638

                            
                        
639
    /*
640
     * @dev Find the insert position for a new node with the given NICR
641
     * @param _NICR Node's NICR
642
     * @param _prevId Id of previous node for the insert position
643
     * @param _nextId Id of next node for the insert position
644
     * @return The IDs of the previous and next nodes for the insert position
645
     */
646
    function findInsertPosition(
647
        uint256 _NICR,
648
        bytes32 _prevId,
649
        bytes32 _nextId
650
    ) external view override returns (bytes32, bytes32) {
651
        return _findInsertPosition(_NICR, _prevId, _nextId);
652
    }
653

                            
                        
654
    function _findInsertPosition(
655
        uint256 _NICR,
656
        bytes32 _prevId,
657
        bytes32 _nextId
658
    ) internal view returns (bytes32, bytes32) {
659
        bytes32 prevId = _prevId;
660
        bytes32 nextId = _nextId;
661

                            
                        
662
        if (prevId != dummyId) {
663
            if (!contains(prevId) || _NICR > cdpManager.getNominalICR(prevId)) {
664
                // `prevId` does not exist anymore or now has a smaller NICR than the given NICR
665
                prevId = dummyId;
666
            }
667
        }
668

                            
                        
669
        if (nextId != dummyId) {
670
            if (!contains(nextId) || _NICR < cdpManager.getNominalICR(nextId)) {
671
                // `nextId` does not exist anymore or now has a larger NICR than the given NICR
672
                nextId = dummyId;
673
            }
674
        }
675

                            
                        
676
        if (prevId == dummyId && nextId == dummyId) {
677
            // No hint - descend list starting from head
678
            return _descendList(_NICR, data.head);
679
        } else if (prevId == dummyId) {
680
            // No `prevId` for hint - ascend list starting from `nextId`
681
            return _ascendList(_NICR, nextId);
682
        } else if (nextId == dummyId) {
683
            // No `nextId` for hint - descend list starting from `prevId`
684
            return _descendList(_NICR, prevId);
685
        } else {
686
            // Descend list starting from `prevId`
687
            return _descendList(_NICR, prevId);
688
        }
689
    }
690

                            
                        
691
    // --- 'require' functions ---
692

                            
                        
693
    /// @dev Asserts that the caller of the function is the CdpManager
694
    function _requireCallerIsCdpManager() internal view {
695
        require(msg.sender == address(cdpManager), "SortedCdps: Caller is not the CdpManager");
696
    }
697

                            
                        
698
    /// @dev Asserts that the caller of the function is either the BorrowerOperations contract or the CdpManager
699
    function _requireCallerIsBOorCdpM() internal view {
700
        require(
701
            msg.sender == borrowerOperationsAddress || msg.sender == address(cdpManager),
702
            "SortedCdps: Caller is neither BO nor CdpM"
703
        );
704
    }
705
}
706

                            
                        

Lines covered: 33 / 41 (80.5%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
import "./Interfaces/IPriceFeed.sol";
6
import "./Interfaces/ICdpManager.sol";
7
import "./Interfaces/ISortedCdps.sol";
8
import "./Interfaces/ICdpManagerData.sol";
9
import "./Dependencies/EbtcBase.sol";
10

                            
                        
11
/// @notice Helper to turn a sequence into CDP id array for batch liquidation
12
contract SyncedLiquidationSequencer is EbtcBase {
13
    ICdpManager public immutable cdpManager;
14
    ISortedCdps public immutable sortedCdps;
15

                            
                        
16
    constructor(
17
        address _cdpManagerAddress,
18
        address _sortedCdpsAddress,
19
        address _priceFeedAddress,
20
        address _activePoolAddress,
21
        address _collateralAddress
22
    ) EbtcBase(_activePoolAddress, _priceFeedAddress, _collateralAddress) {
23
        cdpManager = ICdpManager(_cdpManagerAddress);
24
        sortedCdps = ISortedCdps(_sortedCdpsAddress);
25
    }
26

                            
                        
27
    /// @dev Get first N batch of liquidatable Cdps at current price
28
    /// @dev Non-view function that updates and returns live price at execution time
29
    function sequenceLiqToBatchLiq(uint256 _n) external returns (bytes32[] memory _array) {
30
        uint256 _price = priceFeed.fetchPrice();
31
        return sequenceLiqToBatchLiqWithPrice(_n, _price);
32
    }
33

                            
                        
34
    /// @dev Get first N batch of liquidatable Cdps at specified price
35
    function sequenceLiqToBatchLiqWithPrice(
36
        uint256 _n,
37
        uint256 _price
38
    ) public view returns (bytes32[] memory _array) {
39
        (uint256 _TCR, , ) = _getTCRWithSystemDebtAndCollShares(_price);
40
        bool _recoveryModeAtStart = _TCR < CCR ? true : false;
41
        return _sequenceLiqToBatchLiq(_n, _recoveryModeAtStart, _price);
42
    }
43

                            
                        
44
    // return CdpId array (in NICR-decreasing order same as SortedCdps)
45
    // including the last N CDPs in sortedCdps for batch liquidation
46
    function _sequenceLiqToBatchLiq(
47
        uint256 _n,
48
        bool _recoveryModeAtStart,
49
        uint256 _price
50
    ) internal view returns (bytes32[] memory _array) {
51
        if (_n > 0) {
52
            bytes32 _last = sortedCdps.getLast();
53
            bytes32 _first = sortedCdps.getFirst();
54
            bytes32 _cdpId = _last;
55

                            
                        
56
            uint256 _TCR = cdpManager.getSyncedTCR(_price);
57

                            
                        
58
            // get count of liquidatable CDPs
59
            uint256 _cnt;
60
            for (uint256 i = 0; i < _n && _cdpId != _first; ++i) {
61
                uint256 _icr = cdpManager.getSyncedICR(_cdpId, _price); /// @audit This is view ICR and not real ICR
62
                uint256 _cdpStatus = cdpManager.getCdpStatus(_cdpId);
63
                bool _liquidatable = _canLiquidateInCurrentMode(_recoveryModeAtStart, _icr, _TCR);
64
                if (_liquidatable && _cdpStatus == 1) {
65
                    _cnt += 1;
66
                }
67
                _cdpId = sortedCdps.getPrev(_cdpId);
68
            }
69

                            
                        
70
            // retrieve liquidatable CDPs
71
            _array = new bytes32[](_cnt);
72
            _cdpId = _last;
73
            uint256 _j;
74
            for (uint256 i = 0; i < _n && _cdpId != _first; ++i) {
75
                uint256 _icr = cdpManager.getSyncedICR(_cdpId, _price);
76
                uint256 _cdpStatus = cdpManager.getCdpStatus(_cdpId);
77
                bool _liquidatable = _canLiquidateInCurrentMode(_recoveryModeAtStart, _icr, _TCR);
78
                if (_liquidatable && _cdpStatus == 1) {
79
                    // 1 = ICdpManagerData.Status.active
80
                    _array[_cnt - _j - 1] = _cdpId;
81
                    _j += 1;
82
                }
83
                _cdpId = sortedCdps.getPrev(_cdpId);
84
            }
85
            require(_j == _cnt, "LiquidationLibrary: wrong sequence conversion!");
86
        }
87
    }
88

                            
                        
89
    function _canLiquidateInCurrentMode(
90
        bool _recovery,
91
        uint256 _icr,
92
        uint256 _TCR
93
    ) internal view returns (bool) {
94
        bool _liquidatable = _recovery ? (_icr < MCR || _icr < _TCR) : _icr < MCR;
95

                            
                        
96
        return _liquidatable;
97
    }
98
}
99

                            
                        

Lines covered: 6 / 6 (100.0%)

1
// SPDX-License-Identifier: UNLICENSED
2
pragma solidity 0.8.17;
3

                            
                        
4
import {WETH9} from "./WETH9.sol";
5
import {BorrowerOperations} from "../BorrowerOperations.sol";
6
import {PriceFeedTestnet} from "./testnet/PriceFeedTestnet.sol";
7
import {SortedCdps} from "../SortedCdps.sol";
8
import {CdpManager} from "../CdpManager.sol";
9
import {LiquidationLibrary} from "../LiquidationLibrary.sol";
10
import {LiquidationSequencer} from "../LiquidationSequencer.sol";
11
import {SyncedLiquidationSequencer} from "../SyncedLiquidationSequencer.sol";
12
import {ActivePool} from "../ActivePool.sol";
13
import {HintHelpers} from "../HintHelpers.sol";
14
import {FeeRecipient} from "../FeeRecipient.sol";
15
import {EBTCToken} from "../EBTCToken.sol";
16
import {CollSurplusPool} from "../CollSurplusPool.sol";
17
import {FunctionCaller} from "./FunctionCaller.sol";
18
import {CollateralTokenTester} from "./CollateralTokenTester.sol";
19
import {Governor} from "../Governor.sol";
20
import {EBTCDeployer} from "../EBTCDeployer.sol";
21
import {Actor} from "./invariants/Actor.sol";
22
import {CRLens} from "../CRLens.sol";
23
import {Simulator} from "./invariants/Simulator.sol";
24

                            
                        
25
abstract contract BaseStorageVariables {
26
    PriceFeedTestnet internal priceFeedMock;
27
    SortedCdps internal sortedCdps;
28
    CdpManager internal cdpManager;
29
    WETH9 internal weth;
30
    ActivePool internal activePool;
31
    CollSurplusPool internal collSurplusPool;
32
    FunctionCaller internal functionCaller;
33
    BorrowerOperations internal borrowerOperations;
34
    HintHelpers internal hintHelpers;
35
    EBTCToken internal eBTCToken;
36
    CollateralTokenTester internal collateral;
37
    Governor internal authority;
38
    LiquidationLibrary internal liqudationLibrary;
39
    LiquidationSequencer internal liquidationSequencer;
40
    SyncedLiquidationSequencer internal syncedLiquidationSequencer;
41
    EBTCDeployer internal ebtcDeployer;
42
    address internal defaultGovernance;
43

                            
                        
44
    // LQTY Stuff
45
    FeeRecipient internal feeRecipient;
46

                            
                        
47
    mapping(address => Actor) internal actors;
48
    Actor internal actor;
49

                            
                        
50
    CRLens internal crLens;
51
    Simulator internal simulator;
52

                            
                        
53
    uint internal constant NUMBER_OF_ACTORS = 3;
54
    uint internal constant INITIAL_ETH_BALANCE = 1e24;
55
    uint internal constant INITIAL_COLL_BALANCE = 1e21;
56

                            
                        
57
    uint internal constant diff_tolerance = 0.000000000002e18; //compared to 1e18
58
    uint internal constant MAX_PRICE_CHANGE_PERCENT = 1.05e18; //compared to 1e18
59
    uint internal constant MAX_REBASE_PERCENT = 1.1e18; //compared to 1e18
60
    uint internal constant MAX_FLASHLOAN_ACTIONS = 4;
61
}
62

                            
                        

Lines covered: 58 / 115 (50.4%)

1
// SPDX-License-Identifier: MIT
2
pragma solidity 0.8.17;
3

                            
                        
4
import "../Dependencies/ICollateralToken.sol";
5
import "../Dependencies/ICollateralTokenOracle.sol";
6
import "../Dependencies/Ownable.sol";
7

                            
                        
8
interface IEbtcInternalPool {
9
    function increaseSystemCollShares(uint256 _value) external;
10
}
11

                            
                        
12
// based on WETH9 contract
13
contract CollateralTokenTester is ICollateralToken, ICollateralTokenOracle, Ownable {
14
    string public override name = "Collateral Token Tester in eBTC";
15
    string public override symbol = "CollTester";
16
    uint8 public override decimals = 18;
17

                            
                        
18
    event TransferShares(address indexed from, address indexed to, uint256 sharesValue);
19
    event Deposit(address indexed dst, uint256 wad, uint256 _share);
20
    event Withdrawal(address indexed src, uint256 wad, uint256 _share);
21
    event UncappedMinterAdded(address indexed account);
22
    event UncappedMinterRemoved(address indexed account);
23
    event MintCapSet(uint256 indexed newCap);
24
    event MintCooldownSet(uint256 indexed newCooldown);
25

                            
                        
26
    mapping(address => uint256) private balances;
27
    mapping(address => mapping(address => uint256)) public override allowance;
28
    mapping(address => bool) public isUncappedMinter;
29
    mapping(address => uint256) public lastMintTime;
30

                            
                        
31
    // Faucet capped at 10 Collateral tokens per day
32
    uint256 public mintCap = 10e18;
33
    uint256 public mintCooldown = 60 * 60 * 24;
34

                            
                        
35
    uint256 private _ethPerShare = 1e18;
36
    uint256 private _totalBalance;
37

                            
                        
38
    uint256 private epochsPerFrame = 225;
39
    uint256 private slotsPerEpoch = 32;
40
    uint256 private secondsPerSlot = 12;
41

                            
                        
42
    receive() external payable {
43
        deposit();
44
    }
45

                            
                        
46
    function deposit() public payable {
47
        uint256 _share = getSharesByPooledEth(msg.value);
48
        balances[msg.sender] += _share;
49
        _totalBalance += _share;
50
        emit Deposit(msg.sender, msg.value, _share);
51
    }
52

                            
                        
53
    /// @dev Deposit collateral without ether for testing purposes
54
    function forceDeposit(uint256 ethToDeposit) external {
55
        if (!isUncappedMinter[msg.sender]) {
56
            require(ethToDeposit <= mintCap, "CollTester: Above mint cap");
57
            require(
58
                lastMintTime[msg.sender] == 0 ||
59
                    lastMintTime[msg.sender] + mintCooldown < block.timestamp,
60
                "CollTester: Cooldown period not completed"
61
            );
62
            lastMintTime[msg.sender] = block.timestamp;
63
        }
64
        uint256 _share = getSharesByPooledEth(ethToDeposit);
65
        balances[msg.sender] += _share;
66
        _totalBalance += _share;
67
        emit Deposit(msg.sender, ethToDeposit, _share);
68
    }
69

                            
                        
70
    function withdraw(uint256 wad) public {
71
        uint256 _share = getSharesByPooledEth(wad);
72
        require(balances[msg.sender] >= _share);
73
        balances[msg.sender] -= _share;
74
        _totalBalance -= _share;
75
        payable(msg.sender).transfer(wad);
76
        emit Withdrawal(msg.sender, wad, _share);
77
    }
78

                            
                        
79
    function totalSupply() public view override returns (uint) {
80
        uint _tmp = _mul(_ethPerShare, _totalBalance);
81
        return _div(_tmp, 1e18);
82
    }
83

                            
                        
84
    // Permissioned functions
85
    function addUncappedMinter(address account) external onlyOwner {
86
        isUncappedMinter[account] = true;
87
        emit UncappedMinterAdded(account);
88
    }
89

                            
                        
90
    function removeUncappedMinter(address account) external onlyOwner {
91
        isUncappedMinter[account] = false;
92
        emit UncappedMinterRemoved(account);
93
    }
94

                            
                        
95
    function setMintCap(uint256 newCap) external onlyOwner {
96
        mintCap = newCap;
97
        emit MintCapSet(newCap);
98
    }
99

                            
                        
100
    function setMintCooldown(uint256 newCooldown) external onlyOwner {
101
        mintCooldown = newCooldown;
102
        emit MintCooldownSet(newCooldown);
103
    }
104

                            
                        
105
    // helper to set allowance in test
106
    function nonStandardSetApproval(
107
        address owner,
108
        address guy,
109
        uint256 wad
110
    ) external returns (bool) {
111
        allowance[owner][guy] = wad;
112
        emit Approval(owner, guy, wad);
113
        return true;
114
    }
115

                            
                        
116
    function approve(address guy, uint256 wad) public override returns (bool) {
117
        allowance[msg.sender][guy] = wad;
118
        emit Approval(msg.sender, guy, wad);
119
        return true;
120
    }
121

                            
                        
122
    function transfer(address dst, uint256 wad) public override returns (bool) {
123
        return transferFrom(msg.sender, dst, wad);
124
    }
125

                            
                        
126
    function transferFrom(address src, address dst, uint256 wad) public override returns (bool) {
127
        uint256 _share = getSharesByPooledEth(wad);
128
        require(balances[src] >= _share, "ERC20: transfer amount exceeds balance");
129

                            
                        
130
        if (src != msg.sender && allowance[src][msg.sender] != type(uint256).max) {
131
            require(allowance[src][msg.sender] >= wad);
132
            allowance[src][msg.sender] -= wad;
133
        }
134

                            
                        
135
        balances[src] -= _share;
136
        balances[dst] += _share;
137

                            
                        
138
        _emitTransferEvents(src, dst, wad, _share);
139

                            
                        
140
        return true;
141
    }
142

                            
                        
143
    // tests should adjust the ratio by this function
144
    function setEthPerShare(uint256 _ePerS) external {
145
        _ethPerShare = _ePerS;
146
    }
147

                            
                        
148
    function getEthPerShare() external view returns (uint256) {
149
        return _ethPerShare;
150
    }
151

                            
                        
152
    function getSharesByPooledEth(uint256 _ethAmount) public view override returns (uint256) {
153
        uint256 _tmp = _mul(1e18, _ethAmount);
154
        return _div(_tmp, _ethPerShare);
155
    }
156

                            
                        
157
    function getPooledEthByShares(uint256 _sharesAmount) public view override returns (uint256) {
158
        uint256 _tmp = _mul(_ethPerShare, _sharesAmount);
159
        return _div(_tmp, 1e18);
160
    }
161

                            
                        
162
    function transferShares(
163
        address _recipient,
164
        uint256 _sharesAmount
165
    ) public override returns (uint256) {
166
        uint256 _tknAmt = getPooledEthByShares(_sharesAmount);
167

                            
                        
168
        // NOTE: Changed here to transfer underlying shares without rounding
169
        balances[msg.sender] -= _sharesAmount;
170
        balances[_recipient] += _sharesAmount;
171

                            
                        
172
        _emitTransferEvents(msg.sender, _recipient, _tknAmt, _sharesAmount);
173

                            
                        
174
        return _tknAmt;
175
    }
176

                            
                        
177
    function sharesOf(address _account) public view override returns (uint256) {
178
        return balances[_account];
179
    }
180

                            
                        
181
    function getOracle() external view override returns (address) {
182
        return address(this);
183
    }
184

                            
                        
185
    function getBeaconSpec() public view override returns (uint64, uint64, uint64, uint64) {
186
        return (
187
            uint64(epochsPerFrame),
188
            uint64(slotsPerEpoch),
189
            uint64(secondsPerSlot),
190
            uint64(block.timestamp)
191
        );
192
    }
193

                            
                        
194
    function setBeaconSpec(
195
        uint64 _epochsPerFrame,
196
        uint64 _slotsPerEpoch,
197
        uint64 _secondsPerSlot
198
    ) external {
199
        epochsPerFrame = _epochsPerFrame;
200
        slotsPerEpoch = _slotsPerEpoch;
201
        secondsPerSlot = _secondsPerSlot;
202
    }
203

                            
                        
204
    function decreaseAllowance(
205
        address spender,
206
        uint256 subtractedValue
207
    ) external override returns (bool) {
208
        approve(spender, allowance[msg.sender][spender] - subtractedValue);
209
        return true;
210
    }
211

                            
                        
212
    function balanceOf(address _usr) external view override returns (uint256) {
213
        uint256 _tmp = _mul(_ethPerShare, balances[_usr]);
214
        return _div(_tmp, 1e18);
215
    }
216

                            
                        
217
    function increaseAllowance(
218
        address spender,
219
        uint256 addedValue
220
    ) external override returns (bool) {
221
        approve(spender, allowance[msg.sender][spender] + addedValue);
222
        return true;
223
    }
224

                            
                        
225
    // internal helper functions
226
    function _mul(uint256 a, uint256 b) internal pure returns (uint256) {
227
        if (a == 0) {
228
            return 0;
229
        }
230
        uint256 c = a * b;
231
        require(c / a == b, "SafeMath: multiplication overflow");
232
        return c;
233
    }
234

                            
                        
235
    function _div(uint256 a, uint256 b) internal pure returns (uint256) {
236
        require(b > 0, "SafeMath: zero denominator");
237
        uint256 c = a / b;
238
        return c;
239
    }
240

                            
                        
241
    // dummy test purpose
242
    function feeRecipientAddress() external view returns (address) {
243
        return address(this);
244
    }
245

                            
                        
246
    function authority() external view returns (address) {
247
        return address(this);
248
    }
249

                            
                        
250
    /**
251
     * @dev Emits {Transfer} and {TransferShares} events
252
     */
253
    function _emitTransferEvents(
254
        address _from,
255
        address _to,
256
        uint _tokenAmount,
257
        uint256 _sharesAmount
258
    ) internal {
259
        emit Transfer(_from, _to, _tokenAmount);
260
        emit TransferShares(_from, _to, _sharesAmount);
261
    }
262
}
263

                            
                        

Lines covered: 2 / 15 (13.3%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
import "../EBTCToken.sol";
6

                            
                        
7
contract EBTCTokenTester is EBTCToken {
8
    bytes32 private immutable _PERMIT_TYPEHASH =
9
        0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
10

                            
                        
11
    constructor(
12
        address _cdpManagerAddress,
13
        address _borrowerOperationsAddress,
14
        address _authorityAddress
15
    ) EBTCToken(_cdpManagerAddress, _borrowerOperationsAddress, _authorityAddress) {}
16

                            
                        
17
    function unprotectedMint(address _account, uint256 _amount) external {
18
        // No check on caller here
19

                            
                        
20
        _mint(_account, _amount);
21
    }
22

                            
                        
23
    function unprotectedBurn(address _account, uint256 _amount) external {
24
        // No check on caller here
25

                            
                        
26
        _burn(_account, _amount);
27
    }
28

                            
                        
29
    function unprotectedSendToPool(address _sender, address _poolAddress, uint256 _amount) external {
30
        // No check on caller here
31

                            
                        
32
        _transfer(_sender, _poolAddress, _amount);
33
    }
34

                            
                        
35
    function unprotectedReturnFromPool(
36
        address _poolAddress,
37
        address _receiver,
38
        uint256 _amount
39
    ) external {
40
        // No check on caller here
41

                            
                        
42
        _transfer(_poolAddress, _receiver, _amount);
43
    }
44

                            
                        
45
    function callInternalApprove(address owner, address spender, uint256 amount) external {
46
        _approve(owner, spender, amount);
47
    }
48

                            
                        
49
    function getChainId() external view returns (uint256 chainID) {
50
        //return _chainID(); // it’s private
51
        assembly {
52
            chainID := chainid()
53
        }
54
    }
55

                            
                        
56
    function getDigest(
57
        address owner,
58
        address spender,
59
        uint256 amount,
60
        uint256 nonce,
61
        uint256 deadline
62
    ) external view returns (bytes32) {
63
        return
64
            keccak256(
65
                abi.encodePacked(
66
                    uint16(0x1901),
67
                    domainSeparator(),
68
                    keccak256(abi.encode(_PERMIT_TYPEHASH, owner, spender, amount, nonce, deadline))
69
                )
70
            );
71
    }
72

                            
                        
73
    function recoverAddress(
74
        bytes32 digest,
75
        uint8 v,
76
        bytes32 r,
77
        bytes32 s
78
    ) external pure returns (address) {
79
        return ecrecover(digest, v, r, s);
80
    }
81
}
82

                            
                        

Lines covered: 0 / 14 (0.0%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
import "../Interfaces/ICdpManager.sol";
6
import "../Interfaces/ISortedCdps.sol";
7
import "../Interfaces/IPriceFeed.sol";
8
import "../Dependencies/EbtcMath.sol";
9

                            
                        
10
/* Wrapper contract - used for calculating gas of read-only and internal functions. 
11
Not part of the Liquity application. */
12
contract FunctionCaller {
13
    ICdpManager cdpManager;
14
    address public cdpManagerAddress;
15

                            
                        
16
    ISortedCdps sortedCdps;
17
    address public sortedCdpsAddress;
18

                            
                        
19
    IPriceFeed priceFeed;
20
    address public priceFeedAddress;
21

                            
                        
22
    // --- Dependency setters ---
23

                            
                        
24
    function setCdpManagerAddress(address _cdpManagerAddress) external {
25
        cdpManagerAddress = _cdpManagerAddress;
26
        cdpManager = ICdpManager(_cdpManagerAddress);
27
    }
28

                            
                        
29
    function setSortedCdpsAddress(address _sortedCdpsAddress) external {
30
        cdpManagerAddress = _sortedCdpsAddress;
31
        sortedCdps = ISortedCdps(_sortedCdpsAddress);
32
    }
33

                            
                        
34
    function setPriceFeedAddress(address _priceFeedAddress) external {
35
        priceFeedAddress = _priceFeedAddress;
36
        priceFeed = IPriceFeed(_priceFeedAddress);
37
    }
38

                            
                        
39
    // --- Non-view wrapper functions used for calculating gas ---
40

                            
                        
41
    function cdpManager_getICR(bytes32 _cdpId, uint256 _price) external view returns (uint256) {
42
        return cdpManager.getICR(_cdpId, _price);
43
    }
44

                            
                        
45
    function sortedCdps_findInsertPosition(
46
        uint256 _NICR,
47
        bytes32 _prevId,
48
        bytes32 _nextId
49
    ) external view returns (bytes32, bytes32) {
50
        return sortedCdps.findInsertPosition(_NICR, _prevId, _nextId);
51
    }
52
}
53

                            
                        

Lines covered: 0 / 91 (0.0%)

1
// https://github.com/one-hundred-proof/kyberswap-exploit/blob/main/lib/helpers/Pretty.sol
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
library Strings {
6
    function concat(
7
        string memory _base,
8
        string memory _value
9
    ) internal pure returns (string memory) {
10
        bytes memory _baseBytes = bytes(_base);
11
        bytes memory _valueBytes = bytes(_value);
12

                            
                        
13
        string memory _tmpValue = new string(_baseBytes.length + _valueBytes.length);
14
        bytes memory _newValue = bytes(_tmpValue);
15

                            
                        
16
        uint i;
17
        uint j;
18

                            
                        
19
        for (i = 0; i < _baseBytes.length; i++) {
20
            _newValue[j++] = _baseBytes[i];
21
        }
22

                            
                        
23
        for (i = 0; i < _valueBytes.length; i++) {
24
            _newValue[j++] = _valueBytes[i];
25
        }
26

                            
                        
27
        return string(_newValue);
28
    }
29
}
30

                            
                        
31
library Pretty {
32
    uint8 constant DEFAULT_DECIMALS = 18;
33

                            
                        
34
    function toBitString(uint256 n) external pure returns (string memory) {
35
        return uintToBitString(n, 256);
36
    }
37

                            
                        
38
    function toBitString(uint256 n, uint8 decimals) external pure returns (string memory) {
39
        return uintToBitString(n, decimals);
40
    }
41

                            
                        
42
    function pretty(uint256 n) external pure returns (string memory) {
43
        return
44
            n == type(uint256).max ? "type(uint256).max" : n == type(uint128).max
45
                ? "type(uint128).max"
46
                : _pretty(n, DEFAULT_DECIMALS);
47
    }
48

                            
                        
49
    function pretty(bool value) external pure returns (string memory) {
50
        return value ? "true" : "false";
51
    }
52

                            
                        
53
    function pretty(uint256 n, uint8 decimals) external pure returns (string memory) {
54
        return _pretty(n, decimals);
55
    }
56

                            
                        
57
    function pretty(int256 n) external pure returns (string memory) {
58
        return _prettyInt(n, DEFAULT_DECIMALS);
59
    }
60

                            
                        
61
    function pretty(int256 n, uint8 decimals) external pure returns (string memory) {
62
        return _prettyInt(n, decimals);
63
    }
64

                            
                        
65
    function _pretty(uint256 n, uint8 decimals) internal pure returns (string memory) {
66
        bool pastDecimals = decimals == 0;
67
        uint256 place = 0;
68
        uint256 r; // remainder
69
        string memory s = "";
70

                            
                        
71
        while (n != 0) {
72
            r = n % 10;
73
            n /= 10;
74
            place++;
75
            s = Strings.concat(toDigit(r), s);
76
            if (pastDecimals && place % 3 == 0 && n != 0) {
77
                s = Strings.concat("_", s);
78
            }
79
            if (!pastDecimals && place == decimals) {
80
                pastDecimals = true;
81
                place = 0;
82
                s = Strings.concat("_", s);
83
            }
84
        }
85
        if (pastDecimals && place == 0) {
86
            s = Strings.concat("0", s);
87
        }
88
        if (!pastDecimals) {
89
            uint256 i;
90
            uint256 upper = (decimals >= place ? decimals - place : 0);
91
            for (i = 0; i < upper; ++i) {
92
                s = Strings.concat("0", s);
93
            }
94
            s = Strings.concat("0_", s);
95
        }
96
        return s;
97
    }
98

                            
                        
99
    function _prettyInt(int256 n, uint8 decimals) internal pure returns (string memory) {
100
        bool isNegative = n < 0;
101
        string memory s = "";
102
        if (isNegative) {
103
            s = "-";
104
        }
105
        return Strings.concat(s, _pretty(uint256(isNegative ? -n : n), decimals));
106
    }
107

                            
                        
108
    function toDigit(uint256 n) internal pure returns (string memory) {
109
        if (n == 0) {
110
            return "0";
111
        } else if (n == 1) {
112
            return "1";
113
        } else if (n == 2) {
114
            return "2";
115
        } else if (n == 3) {
116
            return "3";
117
        } else if (n == 4) {
118
            return "4";
119
        } else if (n == 5) {
120
            return "5";
121
        } else if (n == 6) {
122
            return "6";
123
        } else if (n == 7) {
124
            return "7";
125
        } else if (n == 8) {
126
            return "8";
127
        } else if (n == 9) {
128
            return "9";
129
        } else {
130
            revert("Not in range 0 to 10");
131
        }
132
    }
133

                            
                        
134
    function uintToBitString(uint256 n, uint16 bits) internal pure returns (string memory) {
135
        string memory s = "";
136
        for (uint256 i; i < bits; i++) {
137
            if (n % 2 == 0) {
138
                s = Strings.concat("0", s);
139
            } else {
140
                s = Strings.concat("1", s);
141
            }
142
            n = n / 2;
143
        }
144
        return s;
145
    }
146
}
147

                            
                        

Lines covered: 0 / 29 (0.0%)

1
/**
2
 *Submitted for verification at Etherscan.io on 2017-12-12
3
 */
4

                            
                        
5
// Copyright (C) 2015, 2016, 2017 Dapphub
6

                            
                        
7
// This program is free software: you can redistribute it and/or modify
8
// it under the terms of the GNU General Public License as published by
9
// the Free Software Foundation, either version 3 of the License, or
10
// (at your option) any later version.
11

                            
                        
12
// This program is distributed in the hope that it will be useful,
13
// but WITHOUT ANY WARRANTY; without even the implied warranty of
14
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
// GNU General Public License for more details.
16

                            
                        
17
// You should have received a copy of the GNU General Public License
18
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
19

                            
                        
20
pragma solidity 0.8.17;
21

                            
                        
22
contract WETH9 {
23
    string public name = "Wrapped Ether";
24
    string public symbol = "WETH";
25
    uint8 public decimals = 18;
26

                            
                        
27
    event Approval(address indexed src, address indexed guy, uint256 wad);
28
    event Transfer(address indexed src, address indexed dst, uint256 wad);
29
    event Deposit(address indexed dst, uint256 wad);
30
    event Withdrawal(address indexed src, uint256 wad);
31

                            
                        
32
    mapping(address => uint256) public balanceOf;
33
    mapping(address => mapping(address => uint256)) public allowance;
34

                            
                        
35
    function receive() public payable {
36
        deposit();
37
    }
38

                            
                        
39
    function deposit() public payable {
40
        balanceOf[msg.sender] += msg.value;
41
        emit Deposit(msg.sender, msg.value);
42
    }
43

                            
                        
44
    function withdraw(uint256 wad) public {
45
        require(balanceOf[msg.sender] >= wad);
46
        balanceOf[msg.sender] -= wad;
47
        payable(msg.sender).transfer(wad);
48
        emit Withdrawal(msg.sender, wad);
49
    }
50

                            
                        
51
    function totalSupply() public view returns (uint256) {
52
        return address(this).balance;
53
    }
54

                            
                        
55
    function approve(address guy, uint256 wad) public returns (bool) {
56
        allowance[msg.sender][guy] = wad;
57
        emit Approval(msg.sender, guy, wad);
58
        return true;
59
    }
60

                            
                        
61
    function transfer(address dst, uint256 wad) public virtual returns (bool) {
62
        return transferFrom(msg.sender, dst, wad);
63
    }
64

                            
                        
65
    function transferFrom(address src, address dst, uint256 wad) public returns (bool) {
66
        require(balanceOf[src] >= wad);
67

                            
                        
68
        if (src != msg.sender && allowance[src][msg.sender] != type(uint256).max) {
69
            require(allowance[src][msg.sender] >= wad);
70
            allowance[src][msg.sender] -= wad;
71
        }
72

                            
                        
73
        balanceOf[src] -= wad;
74
        balanceOf[dst] += wad;
75

                            
                        
76
        emit Transfer(src, dst, wad);
77

                            
                        
78
        return true;
79
    }
80
}
81

                            
                        
82
/*
83
                    GNU GENERAL PUBLIC LICENSE
84
                       Version 3, 29 June 2007
85

                            
                        
86
 Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
87
 Everyone is permitted to copy and distribute verbatim copies
88
 of this license document, but changing it is not allowed.
89

                            
                        
90
                            Preamble
91

                            
                        
92
  The GNU General Public License is a free, copyleft license for
93
software and other kinds of works.
94

                            
                        
95
  The licenses for most software and other practical works are designed
96
to take away your freedom to share and change the works.  By contrast,
97
the GNU General Public License is intended to guarantee your freedom to
98
share and change all versions of a program--to make sure it remains free
99
software for all its users.  We, the Free Software Foundation, use the
100
GNU General Public License for most of our software; it applies also to
101
any other work released this way by its authors.  You can apply it to
102
your programs, too.
103

                            
                        
104
  When we speak of free software, we are referring to freedom, not
105
price.  Our General Public Licenses are designed to make sure that you
106
have the freedom to distribute copies of free software (and charge for
107
them if you wish), that you receive source code or can get it if you
108
want it, that you can change the software or use pieces of it in new
109
free programs, and that you know you can do these things.
110

                            
                        
111
  To protect your rights, we need to prevent others from denying you
112
these rights or asking you to surrender the rights.  Therefore, you have
113
certain responsibilities if you distribute copies of the software, or if
114
you modify it: responsibilities to respect the freedom of others.
115

                            
                        
116
  For example, if you distribute copies of such a program, whether
117
gratis or for a fee, you must pass on to the recipients the same
118
freedoms that you received.  You must make sure that they, too, receive
119
or can get the source code.  And you must show them these terms so they
120
know their rights.
121

                            
                        
122
  Developers that use the GNU GPL protect your rights with two steps:
123
(1) assert copyright on the software, and (2) offer you this License
124
giving you legal permission to copy, distribute and/or modify it.
125

                            
                        
126
  For the developers' and authors' protection, the GPL clearly explains
127
that there is no warranty for this free software.  For both users' and
128
authors' sake, the GPL requires that modified versions be marked as
129
changed, so that their problems will not be attributed erroneously to
130
authors of previous versions.
131

                            
                        
132
  Some devices are designed to deny users access to install or run
133
modified versions of the software inside them, although the manufacturer
134
can do so.  This is fundamentally incompatible with the aim of
135
protecting users' freedom to change the software.  The systematic
136
pattern of such abuse occurs in the area of products for individuals to
137
use, which is precisely where it is most unacceptable.  Therefore, we
138
have designed this version of the GPL to prohibit the practice for those
139
products.  If such problems arise substantially in other domains, we
140
stand ready to extend this provision to those domains in future versions
141
of the GPL, as needed to protect the freedom of users.
142

                            
                        
143
  Finally, every program is threatened constantly by software patents.
144
States should not allow patents to restrict development and use of
145
software on general-purpose computers, but in those that do, we wish to
146
avoid the special danger that patents applied to a free program could
147
make it effectively proprietary.  To prevent this, the GPL assures that
148
patents cannot be used to render the program non-free.
149

                            
                        
150
  The precise terms and conditions for copying, distribution and
151
modification follow.
152

                            
                        
153
                       TERMS AND CONDITIONS
154

                            
                        
155
  0. Definitions.
156

                            
                        
157
  "This License" refers to version 3 of the GNU General Public License.
158

                            
                        
159
  "Copyright" also means copyright-like laws that apply to other kinds of
160
works, such as semiconductor masks.
161

                            
                        
162
  "The Program" refers to any copyrightable work licensed under this
163
License.  Each licensee is addressed as "you".  "Licensees" and
164
"recipients" may be individuals or organizations.
165

                            
                        
166
  To "modify" a work means to copy from or adapt all or part of the work
167
in a fashion requiring copyright permission, other than the making of an
168
exact copy.  The resulting work is called a "modified version" of the
169
earlier work or a work "based on" the earlier work.
170

                            
                        
171
  A "covered work" means either the unmodified Program or a work based
172
on the Program.
173

                            
                        
174
  To "propagate" a work means to do anything with it that, without
175
permission, would make you directly or secondarily liable for
176
infringement under applicable copyright law, except executing it on a
177
computer or modifying a private copy.  Propagation includes copying,
178
distribution (with or without modification), making available to the
179
public, and in some countries other activities as well.
180

                            
                        
181
  To "convey" a work means any kind of propagation that enables other
182
parties to make or receive copies.  Mere interaction with a user through
183
a computer network, with no transfer of a copy, is not conveying.
184

                            
                        
185
  An interactive user interface displays "Appropriate Legal Notices"
186
to the extent that it includes a convenient and prominently visible
187
feature that (1) displays an appropriate copyright notice, and (2)
188
tells the user that there is no warranty for the work (except to the
189
extent that warranties are provided), that licensees may convey the
190
work under this License, and how to view a copy of this License.  If
191
the interface presents a list of user commands or options, such as a
192
menu, a prominent item in the list meets this criterion.
193

                            
                        
194
  1. Source Code.
195

                            
                        
196
  The "source code" for a work means the preferred form of the work
197
for making modifications to it.  "Object code" means any non-source
198
form of a work.
199

                            
                        
200
  A "Standard Interface" means an interface that either is an official
201
standard defined by a recognized standards body, or, in the case of
202
interfaces specified for a particular programming language, one that
203
is widely used among developers working in that language.
204

                            
                        
205
  The "System Libraries" of an executable work include anything, other
206
than the work as a whole, that (a) is included in the normal form of
207
packaging a Major Component, but which is not part of that Major
208
Component, and (b) serves only to enable use of the work with that
209
Major Component, or to implement a Standard Interface for which an
210
implementation is available to the public in source code form.  A
211
"Major Component", in this context, means a major essential component
212
(kernel, window system, and so on) of the specific operating system
213
(if any) on which the executable work runs, or a compiler used to
214
produce the work, or an object code interpreter used to run it.
215

                            
                        
216
  The "Corresponding Source" for a work in object code form means all
217
the source code needed to generate, install, and (for an executable
218
work) run the object code and to modify the work, including scripts to
219
control those activities.  However, it does not include the work's
220
System Libraries, or general-purpose tools or generally available free
221
programs which are used unmodified in performing those activities but
222
which are not part of the work.  For example, Corresponding Source
223
includes interface definition files associated with source files for
224
the work, and the source code for shared libraries and dynamically
225
linked subprograms that the work is specifically designed to require,
226
such as by intimate data communication or control flow between those
227
subprograms and other parts of the work.
228

                            
                        
229
  The Corresponding Source need not include anything that users
230
can regenerate automatically from other parts of the Corresponding
231
Source.
232

                            
                        
233
  The Corresponding Source for a work in source code form is that
234
same work.
235

                            
                        
236
  2. Basic Permissions.
237

                            
                        
238
  All rights granted under this License are granted for the term of
239
copyright on the Program, and are irrevocable provided the stated
240
conditions are met.  This License explicitly affirms your unlimited
241
permission to run the unmodified Program.  The output from running a
242
covered work is covered by this License only if the output, given its
243
content, constitutes a covered work.  This License acknowledges your
244
rights of fair use or other equivalent, as provided by copyright law.
245

                            
                        
246
  You may make, run and propagate covered works that you do not
247
convey, without conditions so long as your license otherwise remains
248
in force.  You may convey covered works to others for the sole purpose
249
of having them make modifications exclusively for you, or provide you
250
with facilities for running those works, provided that you comply with
251
the terms of this License in conveying all material for which you do
252
not control copyright.  Those thus making or running the covered works
253
for you must do so exclusively on your behalf, under your direction
254
and control, on terms that prohibit them from making any copies of
255
your copyrighted material outside their relationship with you.
256

                            
                        
257
  Conveying under any other circumstances is permitted solely under
258
the conditions stated below.  Sublicensing is not allowed; section 10
259
makes it unnecessary.
260

                            
                        
261
  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
262

                            
                        
263
  No covered work shall be deemed part of an effective technological
264
measure under any applicable law fulfilling obligations under article
265
11 of the WIPO copyright treaty adopted on 20 December 1996, or
266
similar laws prohibiting or restricting circumvention of such
267
measures.
268

                            
                        
269
  When you convey a covered work, you waive any legal power to forbid
270
circumvention of technological measures to the extent such circumvention
271
is effected by exercising rights under this License with respect to
272
the covered work, and you disclaim any intention to limit operation or
273
modification of the work as a means of enforcing, against the work's
274
users, your or third parties' legal rights to forbid circumvention of
275
technological measures.
276

                            
                        
277
  4. Conveying Verbatim Copies.
278

                            
                        
279
  You may convey verbatim copies of the Program's source code as you
280
receive it, in any medium, provided that you conspicuously and
281
appropriately publish on each copy an appropriate copyright notice;
282
keep intact all notices stating that this License and any
283
non-permissive terms added in accord with section 7 apply to the code;
284
keep intact all notices of the absence of any warranty; and give all
285
recipients a copy of this License along with the Program.
286

                            
                        
287
  You may charge any price or no price for each copy that you convey,
288
and you may offer support or warranty protection for a fee.
289

                            
                        
290
  5. Conveying Modified Source Versions.
291

                            
                        
292
  You may convey a work based on the Program, or the modifications to
293
produce it from the Program, in the form of source code under the
294
terms of section 4, provided that you also meet all of these conditions:
295

                            
                        
296
    a) The work must carry prominent notices stating that you modified
297
    it, and giving a relevant date.
298

                            
                        
299
    b) The work must carry prominent notices stating that it is
300
    released under this License and any conditions added under section
301
    7.  This requirement modifies the requirement in section 4 to
302
    "keep intact all notices".
303

                            
                        
304
    c) You must license the entire work, as a whole, under this
305
    License to anyone who comes into possession of a copy.  This
306
    License will therefore apply, along with any applicable section 7
307
    additional terms, to the whole of the work, and all its parts,
308
    regardless of how they are packaged.  This License gives no
309
    permission to license the work in any other way, but it does not
310
    invalidate such permission if you have separately received it.
311

                            
                        
312
    d) If the work has interactive user interfaces, each must display
313
    Appropriate Legal Notices; however, if the Program has interactive
314
    interfaces that do not display Appropriate Legal Notices, your
315
    work need not make them do so.
316

                            
                        
317
  A compilation of a covered work with other separate and independent
318
works, which are not by their nature extensions of the covered work,
319
and which are not combined with it such as to form a larger program,
320
in or on a volume of a storage or distribution medium, is called an
321
"aggregate" if the compilation and its resulting copyright are not
322
used to limit the access or legal rights of the compilation's users
323
beyond what the individual works permit.  Inclusion of a covered work
324
in an aggregate does not cause this License to apply to the other
325
parts of the aggregate.
326

                            
                        
327
  6. Conveying Non-Source Forms.
328

                            
                        
329
  You may convey a covered work in object code form under the terms
330
of sections 4 and 5, provided that you also convey the
331
machine-readable Corresponding Source under the terms of this License,
332
in one of these ways:
333

                            
                        
334
    a) Convey the object code in, or embodied in, a physical product
335
    (including a physical distribution medium), accompanied by the
336
    Corresponding Source fixed on a durable physical medium
337
    customarily used for software interchange.
338

                            
                        
339
    b) Convey the object code in, or embodied in, a physical product
340
    (including a physical distribution medium), accompanied by a
341
    written offer, valid for at least three years and valid for as
342
    long as you offer spare parts or customer support for that product
343
    model, to give anyone who possesses the object code either (1) a
344
    copy of the Corresponding Source for all the software in the
345
    product that is covered by this License, on a durable physical
346
    medium customarily used for software interchange, for a price no
347
    more than your reasonable cost of physically performing this
348
    conveying of source, or (2) access to copy the
349
    Corresponding Source from a network server at no charge.
350

                            
                        
351
    c) Convey individual copies of the object code with a copy of the
352
    written offer to provide the Corresponding Source.  This
353
    alternative is allowed only occasionally and noncommercially, and
354
    only if you received the object code with such an offer, in accord
355
    with subsection 6b.
356

                            
                        
357
    d) Convey the object code by offering access from a designated
358
    place (gratis or for a charge), and offer equivalent access to the
359
    Corresponding Source in the same way through the same place at no
360
    further charge.  You need not require recipients to copy the
361
    Corresponding Source along with the object code.  If the place to
362
    copy the object code is a network server, the Corresponding Source
363
    may be on a different server (operated by you or a third party)
364
    that supports equivalent copying facilities, provided you maintain
365
    clear directions next to the object code saying where to find the
366
    Corresponding Source.  Regardless of what server hosts the
367
    Corresponding Source, you remain obligated to ensure that it is
368
    available for as long as needed to satisfy these requirements.
369

                            
                        
370
    e) Convey the object code using peer-to-peer transmission, provided
371
    you inform other peers where the object code and Corresponding
372
    Source of the work are being offered to the general public at no
373
    charge under subsection 6d.
374

                            
                        
375
  A separable portion of the object code, whose source code is excluded
376
from the Corresponding Source as a System Library, need not be
377
included in conveying the object code work.
378

                            
                        
379
  A "User Product" is either (1) a "consumer product", which means any
380
tangible personal property which is normally used for personal, family,
381
or household purposes, or (2) anything designed or sold for incorporation
382
into a dwelling.  In determining whether a product is a consumer product,
383
doubtful cases shall be resolved in favor of coverage.  For a particular
384
product received by a particular user, "normally used" refers to a
385
typical or common use of that class of product, regardless of the status
386
of the particular user or of the way in which the particular user
387
actually uses, or expects or is expected to use, the product.  A product
388
is a consumer product regardless of whether the product has substantial
389
commercial, industrial or non-consumer uses, unless such uses represent
390
the only significant mode of use of the product.
391

                            
                        
392
  "Installation Information" for a User Product means any methods,
393
procedures, authorization keys, or other information required to install
394
and execute modified versions of a covered work in that User Product from
395
a modified version of its Corresponding Source.  The information must
396
suffice to ensure that the continued functioning of the modified object
397
code is in no case prevented or interfered with solely because
398
modification has been made.
399

                            
                        
400
  If you convey an object code work under this section in, or with, or
401
specifically for use in, a User Product, and the conveying occurs as
402
part of a transaction in which the right of possession and use of the
403
User Product is transferred to the recipient in perpetuity or for a
404
fixed term (regardless of how the transaction is characterized), the
405
Corresponding Source conveyed under this section must be accompanied
406
by the Installation Information.  But this requirement does not apply
407
if neither you nor any third party retains the ability to install
408
modified object code on the User Product (for example, the work has
409
been installed in ROM).
410

                            
                        
411
  The requirement to provide Installation Information does not include a
412
requirement to continue to provide support service, warranty, or updates
413
for a work that has been modified or installed by the recipient, or for
414
the User Product in which it has been modified or installed.  Access to a
415
network may be denied when the modification itself materially and
416
adversely affects the operation of the network or violates the rules and
417
protocols for communication across the network.
418

                            
                        
419
  Corresponding Source conveyed, and Installation Information provided,
420
in accord with this section must be in a format that is publicly
421
documented (and with an implementation available to the public in
422
source code form), and must require no special password or key for
423
unpacking, reading or copying.
424

                            
                        
425
  7. Additional Terms.
426

                            
                        
427
  "Additional permissions" are terms that supplement the terms of this
428
License by making exceptions from one or more of its conditions.
429
Additional permissions that are applicable to the entire Program shall
430
be treated as though they were included in this License, to the extent
431
that they are valid under applicable law.  If additional permissions
432
apply only to part of the Program, that part may be used separately
433
under those permissions, but the entire Program remains governed by
434
this License without regard to the additional permissions.
435

                            
                        
436
  When you convey a copy of a covered work, you may at your option
437
remove any additional permissions from that copy, or from any part of
438
it.  (Additional permissions may be written to require their own
439
removal in certain cases when you modify the work.)  You may place
440
additional permissions on material, added by you to a covered work,
441
for which you have or can give appropriate copyright permission.
442

                            
                        
443
  Notwithstanding any other provision of this License, for material you
444
add to a covered work, you may (if authorized by the copyright holders of
445
that material) supplement the terms of this License with terms:
446

                            
                        
447
    a) Disclaiming warranty or limiting liability differently from the
448
    terms of sections 15 and 16 of this License; or
449

                            
                        
450
    b) Requiring preservation of specified reasonable legal notices or
451
    author attributions in that material or in the Appropriate Legal
452
    Notices displayed by works containing it; or
453

                            
                        
454
    c) Prohibiting misrepresentation of the origin of that material, or
455
    requiring that modified versions of such material be marked in
456
    reasonable ways as different from the original version; or
457

                            
                        
458
    d) Limiting the use for publicity purposes of names of licensors or
459
    authors of the material; or
460

                            
                        
461
    e) Declining to grant rights under trademark law for use of some
462
    trade names, trademarks, or service marks; or
463

                            
                        
464
    f) Requiring indemnification of licensors and authors of that
465
    material by anyone who conveys the material (or modified versions of
466
    it) with contractual assumptions of liability to the recipient, for
467
    any liability that these contractual assumptions directly impose on
468
    those licensors and authors.
469

                            
                        
470
  All other non-permissive additional terms are considered "further
471
restrictions" within the meaning of section 10.  If the Program as you
472
received it, or any part of it, contains a notice stating that it is
473
governed by this License along with a term that is a further
474
restriction, you may remove that term.  If a license document contains
475
a further restriction but permits relicensing or conveying under this
476
License, you may add to a covered work material governed by the terms
477
of that license document, provided that the further restriction does
478
not survive such relicensing or conveying.
479

                            
                        
480
  If you add terms to a covered work in accord with this section, you
481
must place, in the relevant source files, a statement of the
482
additional terms that apply to those files, or a notice indicating
483
where to find the applicable terms.
484

                            
                        
485
  Additional terms, permissive or non-permissive, may be stated in the
486
form of a separately written license, or stated as exceptions;
487
the above requirements apply either way.
488

                            
                        
489
  8. Termination.
490

                            
                        
491
  You may not propagate or modify a covered work except as expressly
492
provided under this License.  Any attempt otherwise to propagate or
493
modify it is void, and will automatically terminate your rights under
494
this License (including any patent licenses granted under the third
495
paragraph of section 11).
496

                            
                        
497
  However, if you cease all violation of this License, then your
498
license from a particular copyright holder is reinstated (a)
499
provisionally, unless and until the copyright holder explicitly and
500
finally terminates your license, and (b) permanently, if the copyright
501
holder fails to notify you of the violation by some reasonable means
502
prior to 60 days after the cessation.
503

                            
                        
504
  Moreover, your license from a particular copyright holder is
505
reinstated permanently if the copyright holder notifies you of the
506
violation by some reasonable means, this is the first time you have
507
received notice of violation of this License (for any work) from that
508
copyright holder, and you cure the violation prior to 30 days after
509
your receipt of the notice.
510

                            
                        
511
  Termination of your rights under this section does not terminate the
512
licenses of parties who have received copies or rights from you under
513
this License.  If your rights have been terminated and not permanently
514
reinstated, you do not qualify to receive new licenses for the same
515
material under section 10.
516

                            
                        
517
  9. Acceptance Not Required for Having Copies.
518

                            
                        
519
  You are not required to accept this License in order to receive or
520
run a copy of the Program.  Ancillary propagation of a covered work
521
occurring solely as a consequence of using peer-to-peer transmission
522
to receive a copy likewise does not require acceptance.  However,
523
nothing other than this License grants you permission to propagate or
524
modify any covered work.  These actions infringe copyright if you do
525
not accept this License.  Therefore, by modifying or propagating a
526
covered work, you indicate your acceptance of this License to do so.
527

                            
                        
528
  10. Automatic Licensing of Downstream Recipients.
529

                            
                        
530
  Each time you convey a covered work, the recipient automatically
531
receives a license from the original licensors, to run, modify and
532
propagate that work, subject to this License.  You are not responsible
533
for enforcing compliance by third parties with this License.
534

                            
                        
535
  An "entity transaction" is a transaction transferring control of an
536
organization, or substantially all assets of one, or subdividing an
537
organization, or merging organizations.  If propagation of a covered
538
work results from an entity transaction, each party to that
539
transaction who receives a copy of the work also receives whatever
540
licenses to the work the party's predecessor in interest had or could
541
give under the previous paragraph, plus a right to possession of the
542
Corresponding Source of the work from the predecessor in interest, if
543
the predecessor has it or can get it with reasonable efforts.
544

                            
                        
545
  You may not impose any further restrictions on the exercise of the
546
rights granted or affirmed under this License.  For example, you may
547
not impose a license fee, royalty, or other charge for exercise of
548
rights granted under this License, and you may not initiate litigation
549
(including a cross-claim or counterclaim in a lawsuit) alleging that
550
any patent claim is infringed by making, using, selling, offering for
551
sale, or importing the Program or any portion of it.
552

                            
                        
553
  11. Patents.
554

                            
                        
555
  A "contributor" is a copyright holder who authorizes use under this
556
License of the Program or a work on which the Program is based.  The
557
work thus licensed is called the contributor's "contributor version".
558

                            
                        
559
  A contributor's "essential patent claims" are all patent claims
560
owned or controlled by the contributor, whether already acquired or
561
hereafter acquired, that would be infringed by some manner, permitted
562
by this License, of making, using, or selling its contributor version,
563
but do not include claims that would be infringed only as a
564
consequence of further modification of the contributor version.  For
565
purposes of this definition, "control" includes the right to grant
566
patent sublicenses in a manner consistent with the requirements of
567
this License.
568

                            
                        
569
  Each contributor grants you a non-exclusive, worldwide, royalty-free
570
patent license under the contributor's essential patent claims, to
571
make, use, sell, offer for sale, import and otherwise run, modify and
572
propagate the contents of its contributor version.
573

                            
                        
574
  In the following three paragraphs, a "patent license" is any express
575
agreement or commitment, however denominated, not to enforce a patent
576
(such as an express permission to practice a patent or covenant not to
577
sue for patent infringement).  To "grant" such a patent license to a
578
party means to make such an agreement or commitment not to enforce a
579
patent against the party.
580

                            
                        
581
  If you convey a covered work, knowingly relying on a patent license,
582
and the Corresponding Source of the work is not available for anyone
583
to copy, free of charge and under the terms of this License, through a
584
publicly available network server or other readily accessible means,
585
then you must either (1) cause the Corresponding Source to be so
586
available, or (2) arrange to deprive yourself of the benefit of the
587
patent license for this particular work, or (3) arrange, in a manner
588
consistent with the requirements of this License, to extend the patent
589
license to downstream recipients.  "Knowingly relying" means you have
590
actual knowledge that, but for the patent license, your conveying the
591
covered work in a country, or your recipient's use of the covered work
592
in a country, would infringe one or more identifiable patents in that
593
country that you have reason to believe are valid.
594

                            
                        
595
  If, pursuant to or in connection with a single transaction or
596
arrangement, you convey, or propagate by procuring conveyance of, a
597
covered work, and grant a patent license to some of the parties
598
receiving the covered work authorizing them to use, propagate, modify
599
or convey a specific copy of the covered work, then the patent license
600
you grant is automatically extended to all recipients of the covered
601
work and works based on it.
602

                            
                        
603
  A patent license is "discriminatory" if it does not include within
604
the scope of its coverage, prohibits the exercise of, or is
605
conditioned on the non-exercise of one or more of the rights that are
606
specifically granted under this License.  You may not convey a covered
607
work if you are a party to an arrangement with a third party that is
608
in the business of distributing software, under which you make payment
609
to the third party based on the extent of your activity of conveying
610
the work, and under which the third party grants, to any of the
611
parties who would receive the covered work from you, a discriminatory
612
patent license (a) in connection with copies of the covered work
613
conveyed by you (or copies made from those copies), or (b) primarily
614
for and in connection with specific products or compilations that
615
contain the covered work, unless you entered into that arrangement,
616
or that patent license was granted, prior to 28 March 2007.
617

                            
                        
618
  Nothing in this License shall be construed as excluding or limiting
619
any implied license or other defenses to infringement that may
620
otherwise be available to you under applicable patent law.
621

                            
                        
622
  12. No Surrender of Others' Freedom.
623

                            
                        
624
  If conditions are imposed on you (whether by court order, agreement or
625
otherwise) that contradict the conditions of this License, they do not
626
excuse you from the conditions of this License.  If you cannot convey a
627
covered work so as to satisfy simultaneously your obligations under this
628
License and any other pertinent obligations, then as a consequence you may
629
not convey it at all.  For example, if you agree to terms that obligate you
630
to collect a royalty for further conveying from those to whom you convey
631
the Program, the only way you could satisfy both those terms and this
632
License would be to refrain entirely from conveying the Program.
633

                            
                        
634
  13. Use with the GNU Affero General Public License.
635

                            
                        
636
  Notwithstanding any other provision of this License, you have
637
permission to link or combine any covered work with a work licensed
638
under version 3 of the GNU Affero General Public License into a single
639
combined work, and to convey the resulting work.  The terms of this
640
License will continue to apply to the part which is the covered work,
641
but the special requirements of the GNU Affero General Public License,
642
section 13, concerning interaction through a network will apply to the
643
combination as such.
644

                            
                        
645
  14. Revised Versions of this License.
646

                            
                        
647
  The Free Software Foundation may publish revised and/or new versions of
648
the GNU General Public License from time to time.  Such new versions will
649
be similar in spirit to the present version, but may differ in detail to
650
address new problems or concerns.
651

                            
                        
652
  Each version is given a distinguishing version number.  If the
653
Program specifies that a certain numbered version of the GNU General
654
Public License "or any later version" applies to it, you have the
655
option of following the terms and conditions either of that numbered
656
version or of any later version published by the Free Software
657
Foundation.  If the Program does not specify a version number of the
658
GNU General Public License, you may choose any version ever published
659
by the Free Software Foundation.
660

                            
                        
661
  If the Program specifies that a proxy can decide which future
662
versions of the GNU General Public License can be used, that proxy's
663
public statement of acceptance of a version permanently authorizes you
664
to choose that version for the Program.
665

                            
                        
666
  Later license versions may give you additional or different
667
permissions.  However, no additional obligations are imposed on any
668
author or copyright holder as a result of your choosing to follow a
669
later version.
670

                            
                        
671
  15. Disclaimer of Warranty.
672

                            
                        
673
  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
674
APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
675
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
676
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
677
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
678
PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
679
IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
680
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
681

                            
                        
682
  16. Limitation of Liability.
683

                            
                        
684
  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
685
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
686
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
687
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
688
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
689
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
690
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
691
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
692
SUCH DAMAGES.
693

                            
                        
694
  17. Interpretation of Sections 15 and 16.
695

                            
                        
696
  If the disclaimer of warranty and limitation of liability provided
697
above cannot be given local legal effect according to their terms,
698
reviewing courts shall apply local law that most closely approximates
699
an absolute waiver of all civil liability in connection with the
700
Program, unless a warranty or assumption of liability accompanies a
701
copy of the Program in return for a fee.
702

                            
                        
703
                     END OF TERMS AND CONDITIONS
704

                            
                        
705
            How to Apply These Terms to Your New Programs
706

                            
                        
707
  If you develop a new program, and you want it to be of the greatest
708
possible use to the public, the best way to achieve this is to make it
709
free software which everyone can redistribute and change under these terms.
710

                            
                        
711
  To do so, attach the following notices to the program.  It is safest
712
to attach them to the start of each source file to most effectively
713
state the exclusion of warranty; and each file should have at least
714
the "copyright" line and a pointer to where the full notice is found.
715

                            
                        
716
    <one line to give the program's name and a brief idea of what it does.>
717
    Copyright (C) <year>  <name of author>
718

                            
                        
719
    This program is free software: you can redistribute it and/or modify
720
    it under the terms of the GNU General Public License as published by
721
    the Free Software Foundation, either version 3 of the License, or
722
    (at your option) any later version.
723

                            
                        
724
    This program is distributed in the hope that it will be useful,
725
    but WITHOUT ANY WARRANTY; without even the implied warranty of
726
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
727
    GNU General Public License for more details.
728

                            
                        
729
    You should have received a copy of the GNU General Public License
730
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
731

                            
                        
732
Also add information on how to contact you by electronic and paper mail.
733

                            
                        
734
  If the program does terminal interaction, make it output a short
735
notice like this when it starts in an interactive mode:
736

                            
                        
737
    <program>  Copyright (C) <year>  <name of author>
738
    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
739
    This is free software, and you are welcome to redistribute it
740
    under certain conditions; type `show c' for details.
741

                            
                        
742
The hypothetical commands `show w' and `show c' should show the appropriate
743
parts of the General Public License.  Of course, your program's commands
744
might be different; for a GUI interface, you would use an "about box".
745

                            
                        
746
  You should also get your employer (if you work as a programmer) or school,
747
if any, to sign a "copyright disclaimer" for the program, if necessary.
748
For more information on this, and how to apply and follow the GNU GPL, see
749
<http://www.gnu.org/licenses/>.
750

                            
                        
751
  The GNU General Public License does not permit incorporating your program
752
into proprietary programs.  If your program is a subroutine library, you
753
may consider it more useful to permit linking proprietary applications with
754
the library.  If this is what you want to do, use the GNU Lesser General
755
Public License instead of this License.  But first, please read
756
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
757

                            
                        
758
*/
759

                            
                        

Lines covered: 8 / 23 (34.8%)

1
pragma solidity 0.8.17;
2

                            
                        
3
import {IERC3156FlashBorrower} from "../../Interfaces/IERC3156FlashBorrower.sol";
4
import {IERC20} from "../../Dependencies/IERC20.sol";
5

                            
                        
6
contract Actor is IERC3156FlashBorrower {
7
    address[] internal tokens;
8
    address[] internal callers;
9

                            
                        
10
    constructor(address[] memory _tokens, address[] memory _callers) payable {
11
        tokens = _tokens;
12
        callers = _callers;
13
        for (uint256 i = 0; i < tokens.length; i++) {
14
            IERC20(tokens[i]).approve(callers[i], type(uint256).max);
15
        }
16
    }
17

                            
                        
18
    function proxy(
19
        address _target,
20
        bytes memory _calldata
21
    ) public returns (bool success, bytes memory returnData) {
22
        (success, returnData) = address(_target).call(_calldata);
23
    }
24

                            
                        
25
    function proxy(
26
        address _target,
27
        bytes memory _calldata,
28
        uint256 value
29
    ) public returns (bool success, bytes memory returnData) {
30
        (success, returnData) = address(_target).call{value: value}(_calldata);
31
    }
32

                            
                        
33
    receive() external payable {}
34

                            
                        
35
    // callback for flashloan
36
    function onFlashLoan(
37
        address,
38
        address token,
39
        uint256 amount,
40
        uint256 fee,
41
        bytes calldata data
42
    ) external override returns (bytes32) {
43
        bool isValidCaller = false;
44
        for (uint256 i = 0; i < tokens.length; i++) {
45
            if (token == tokens[i]) {
46
                isValidCaller = msg.sender == callers[i];
47
                break;
48
            }
49
        }
50
        require(isValidCaller, "Invalid caller");
51

                            
                        
52
        if (data.length != 0) {
53
            (address[] memory _targets, bytes[] memory _calldatas) = abi.decode(
54
                data,
55
                (address[], bytes[])
56
            );
57
            for (uint256 i = 0; i < _targets.length; ++i) {
58
                (bool success, ) = address(_targets[i]).call(_calldatas[i]);
59
                require(success);
60
            }
61
        }
62

                            
                        
63
        IERC20(token).approve(msg.sender, amount + fee);
64

                            
                        
65
        return keccak256("ERC3156FlashBorrower.onFlashLoan");
66
    }
67
}
68

                            
                        

Lines covered: 20 / 31 (64.5%)

1
pragma solidity 0.8.17;
2

                            
                        
3
abstract contract Asserts {
4
    event L1(uint256);
5
    event L2(uint256, uint256);
6
    event L3(uint256, uint256, uint256);
7
    event L4(uint256, uint256, uint256, uint256);
8

                            
                        
9
    function gt(uint256 a, uint256 b, string memory reason) internal virtual;
10

                            
                        
11
    function gte(uint256 a, uint256 b, string memory reason) internal virtual;
12

                            
                        
13
    function lt(uint256 a, uint256 b, string memory reason) internal virtual;
14

                            
                        
15
    function lte(uint256 a, uint256 b, string memory reason) internal virtual;
16

                            
                        
17
    function eq(uint256 a, uint256 b, string memory reason) internal virtual;
18

                            
                        
19
    function t(bool b, string memory reason) internal virtual;
20

                            
                        
21
    function between(uint256 value, uint256 low, uint256 high) internal virtual returns (uint256);
22

                            
                        
23
    function isApproximateEq(
24
        uint256 _num1,
25
        uint256 _num2,
26
        uint256 _tolerance
27
    ) internal pure returns (bool) {
28
        return diffPercent(_num1, _num2) <= _tolerance;
29
    }
30

                            
                        
31
    function diffPercent(uint256 _num1, uint256 _num2) internal pure returns (uint256) {
32
        if (_num1 == _num2) return 0;
33
        else if (_num1 > _num2) {
34
            return ((_num1 - _num2) * 1e18) / ((_num1 + _num2) / 2);
35
        } else {
36
            return ((_num2 - _num1) * 1e18) / ((_num1 + _num2) / 2);
37
        }
38
    }
39

                            
                        
40
    // https://ethereum.stackexchange.com/a/83577
41
    function _getRevertMsg(bytes memory returnData) internal pure returns (string memory) {
42
        // Check that the data has the right size: 4 bytes for signature + 32 bytes for panic code
43
        if (returnData.length == 4 + 32) {
44
            // Check that the data starts with the Panic signature
45
            bytes4 panicSignature = bytes4(keccak256(bytes("Panic(uint256)")));
46
            for (uint i = 0; i < 4; i++) {
47
                if (returnData[i] != panicSignature[i]) return "Undefined signature";
48
            }
49

                            
                        
50
            uint256 panicCode;
51
            for (uint i = 4; i < 36; i++) {
52
                panicCode = panicCode << 8;
53
                panicCode |= uint8(returnData[i]);
54
            }
55

                            
                        
56
            // Now convert the panic code into its string representation
57
            if (panicCode == 17) {
58
                return "Panic(17)";
59
            }
60

                            
                        
61
            // Add other panic codes as needed or return a generic "Unknown panic"
62
            return "Undefined panic code";
63
        }
64

                            
                        
65
        // If the returnData length is less than 68, then the transaction failed silently (without a revert message)
66
        if (returnData.length < 68) return "Transaction reverted silently";
67

                            
                        
68
        assembly {
69
            // Slice the sighash.
70
            returnData := add(returnData, 0x04)
71
        }
72
        return abi.decode(returnData, (string)); // All that remains is the revert string
73
    }
74

                            
                        
75
    function _isRevertReasonEqual(
76
        bytes memory returnData,
77
        string memory reason
78
    ) internal pure returns (bool) {
79
        return (keccak256(abi.encodePacked(_getRevertMsg(returnData))) ==
80
            keccak256(abi.encodePacked(reason)));
81
    }
82

                            
                        
83
    function max(uint256 a, uint256 b) internal pure returns (uint256) {
84
        return a >= b ? a : b;
85
    }
86

                            
                        
87
    function min(uint256 a, uint256 b) internal pure returns (uint256) {
88
        return a < b ? a : b;
89
    }
90

                            
                        
91
    function assertRevertReasonNotEqual(bytes memory returnData, string memory reason) internal {
92
        bool isEqual = _isRevertReasonEqual(returnData, reason);
93
        t(!isEqual, reason);
94
    }
95

                            
                        
96
    function assertRevertReasonEqual(bytes memory returnData, string memory reason) internal {
97
        bool isEqual = _isRevertReasonEqual(returnData, reason);
98
        t(isEqual, reason);
99
    }
100

                            
                        
101
    function assertRevertReasonEqual(
102
        bytes memory returnData,
103
        string memory reason1,
104
        string memory reason2
105
    ) internal {
106
        bool isEqual = _isRevertReasonEqual(returnData, reason1) ||
107
            _isRevertReasonEqual(returnData, reason2);
108
        t(isEqual, string.concat(reason1, " OR ", reason2));
109
    }
110

                            
                        
111
    function assertRevertReasonEqual(
112
        bytes memory returnData,
113
        string memory reason1,
114
        string memory reason2,
115
        string memory reason3
116
    ) internal {
117
        bool isEqual = _isRevertReasonEqual(returnData, reason1) ||
118
            _isRevertReasonEqual(returnData, reason2) ||
119
            _isRevertReasonEqual(returnData, reason3);
120
        t(isEqual, string.concat(reason1, " OR ", reason2, " OR ", reason3));
121
    }
122

                            
                        
123
    function assertRevertReasonEqual(
124
        bytes memory returnData,
125
        string memory reason1,
126
        string memory reason2,
127
        string memory reason3,
128
        string memory reason4
129
    ) internal {
130
        bool isEqual = _isRevertReasonEqual(returnData, reason1) ||
131
            _isRevertReasonEqual(returnData, reason2) ||
132
            _isRevertReasonEqual(returnData, reason3) ||
133
            _isRevertReasonEqual(returnData, reason4);
134
        t(isEqual, string.concat(reason1, " OR ", reason2, " OR ", reason3, " OR ", reason4));
135
    }
136
}
137

                            
                        

Lines covered: 90 / 90 (100.0%)

1
pragma solidity 0.8.17;
2

                            
                        
3
import {Pretty, Strings} from "../Pretty.sol";
4
import {BaseStorageVariables} from "../BaseStorageVariables.sol";
5

                            
                        
6
abstract contract BeforeAfter is BaseStorageVariables {
7
    using Strings for string;
8
    using Pretty for uint256;
9
    using Pretty for int256;
10
    using Pretty for bool;
11

                            
                        
12
    struct Vars {
13
        uint256 valueInSystemBefore;
14
        uint256 valueInSystemAfter;
15
        uint256 nicrBefore;
16
        uint256 nicrAfter;
17
        uint256 icrBefore;
18
        uint256 icrAfter;
19
        uint256 newIcrBefore;
20
        uint256 newIcrAfter;
21
        uint256 feeSplitBefore;
22
        uint256 feeSplitAfter;
23
        uint256 feeRecipientTotalCollBefore;
24
        uint256 feeRecipientTotalCollAfter;
25
        uint256 feeRecipientCollSharesBefore;
26
        uint256 feeRecipientCollSharesAfter;
27
        uint256 actorCollBefore;
28
        uint256 actorCollAfter;
29
        uint256 actorEbtcBefore;
30
        uint256 actorEbtcAfter;
31
        uint256 actorCdpCountBefore;
32
        uint256 actorCdpCountAfter;
33
        uint256 cdpCollBefore;
34
        uint256 cdpCollAfter;
35
        uint256 cdpDebtBefore;
36
        uint256 cdpDebtAfter;
37
        uint256 liquidatorRewardSharesBefore;
38
        uint256 liquidatorRewardSharesAfter;
39
        uint256 sortedCdpsSizeBefore;
40
        uint256 sortedCdpsSizeAfter;
41
        uint256 cdpStatusBefore;
42
        uint256 cdpStatusAfter;
43
        uint256 tcrBefore;
44
        uint256 tcrAfter;
45
        uint256 newTcrBefore;
46
        uint256 newTcrAfter;
47
        uint256 ebtcTotalSupplyBefore;
48
        uint256 ebtcTotalSupplyAfter;
49
        uint256 ethPerShareBefore;
50
        uint256 ethPerShareAfter;
51
        uint256 activePoolCollBefore;
52
        uint256 activePoolCollAfter;
53
        uint256 activePoolDebtBefore;
54
        uint256 activePoolDebtAfter;
55
        uint256 collSurplusPoolBefore;
56
        uint256 collSurplusPoolAfter;
57
        uint256 priceBefore;
58
        uint256 priceAfter;
59
        bool isRecoveryModeBefore;
60
        bool isRecoveryModeAfter;
61
        uint256 lastGracePeriodStartTimestampBefore;
62
        uint256 lastGracePeriodStartTimestampAfter;
63
        bool lastGracePeriodStartTimestampIsSetBefore;
64
        bool lastGracePeriodStartTimestampIsSetAfter;
65
        bool hasGracePeriodPassedBefore;
66
        bool hasGracePeriodPassedAfter;
67
        uint256 systemDebtRedistributionIndexBefore;
68
        uint256 systemDebtRedistributionIndexAfter;
69
    }
70

                            
                        
71
    Vars vars;
72
    struct Cdp {
73
        bytes32 id;
74
        uint256 icr;
75
    }
76

                            
                        
77
    function _before(bytes32 _cdpId) internal {
78
        vars.priceBefore = priceFeedMock.fetchPrice();
79

                            
                        
80
        (uint256 debtBefore, , ) = cdpManager.getDebtAndCollShares(_cdpId);
81

                            
                        
82
        vars.nicrBefore = _cdpId != bytes32(0) ? crLens.quoteRealNICR(_cdpId) : 0;
83
        vars.icrBefore = _cdpId != bytes32(0) ? cdpManager.getICR(_cdpId, vars.priceBefore) : 0;
84
        vars.cdpCollBefore = _cdpId != bytes32(0) ? cdpManager.getCdpCollShares(_cdpId) : 0;
85
        vars.cdpDebtBefore = _cdpId != bytes32(0) ? debtBefore : 0;
86
        vars.liquidatorRewardSharesBefore = _cdpId != bytes32(0)
87
            ? cdpManager.getCdpLiquidatorRewardShares(_cdpId)
88
            : 0;
89
        vars.cdpStatusBefore = _cdpId != bytes32(0) ? cdpManager.getCdpStatus(_cdpId) : 0;
90

                            
                        
91
        vars.isRecoveryModeBefore = crLens.quoteCheckRecoveryMode() == 1; /// @audit crLens
92
        (vars.feeSplitBefore, , ) = collateral.getPooledEthByShares(cdpManager.DECIMAL_PRECISION()) >
93
            cdpManager.stEthIndex()
94
            ? cdpManager.calcFeeUponStakingReward(
95
                collateral.getPooledEthByShares(cdpManager.DECIMAL_PRECISION()),
96
                cdpManager.stEthIndex()
97
            )
98
            : (0, 0, 0);
99
        vars.feeRecipientTotalCollBefore =
100
            activePool.getFeeRecipientClaimableCollShares() +
101
            collateral.balanceOf(activePool.feeRecipientAddress());
102
        vars.feeRecipientCollSharesBefore = activePool.getFeeRecipientClaimableCollShares();
103
        vars.actorCollBefore = collateral.balanceOf(address(actor));
104
        vars.actorEbtcBefore = eBTCToken.balanceOf(address(actor));
105
        vars.actorCdpCountBefore = sortedCdps.cdpCountOf(address(actor));
106
        vars.sortedCdpsSizeBefore = sortedCdps.getSize();
107
        vars.tcrBefore = cdpManager.getTCR(vars.priceBefore);
108
        vars.ebtcTotalSupplyBefore = eBTCToken.totalSupply();
109
        vars.ethPerShareBefore = collateral.getEthPerShare();
110
        vars.activePoolDebtBefore = activePool.getSystemDebt();
111
        vars.activePoolCollBefore = activePool.getSystemCollShares();
112
        vars.collSurplusPoolBefore = collSurplusPool.getTotalSurplusCollShares();
113
        vars.lastGracePeriodStartTimestampBefore = cdpManager.lastGracePeriodStartTimestamp();
114
        vars.lastGracePeriodStartTimestampIsSetBefore =
115
            cdpManager.lastGracePeriodStartTimestamp() != cdpManager.UNSET_TIMESTAMP();
116
        vars.hasGracePeriodPassedBefore =
117
            cdpManager.lastGracePeriodStartTimestamp() != cdpManager.UNSET_TIMESTAMP() &&
118
            block.timestamp >
119
            cdpManager.lastGracePeriodStartTimestamp() +
120
                cdpManager.recoveryModeGracePeriodDuration();
121
        vars.systemDebtRedistributionIndexBefore = cdpManager.systemDebtRedistributionIndex();
122
        // TODO: Cleanup new vs old
123
        vars.newTcrBefore = crLens.quoteRealTCR();
124
        vars.newIcrBefore = crLens.quoteRealICR(_cdpId);
125

                            
                        
126
        vars.valueInSystemBefore == (collateral.getPooledEthByShares
127
        (vars.activePoolCollBefore +
128
            vars.collSurplusPoolBefore +
129
            vars.feeRecipientTotalCollBefore) * vars.priceBefore) /
130
            1e18 -
131
            vars.activePoolDebtBefore;
132
    }
133

                            
                        
134
    function _after(bytes32 _cdpId) internal {
135
        vars.priceAfter = priceFeedMock.fetchPrice();
136

                            
                        
137
        vars.nicrAfter = _cdpId != bytes32(0) ? crLens.quoteRealNICR(_cdpId) : 0;
138
        vars.icrAfter = _cdpId != bytes32(0) ? cdpManager.getICR(_cdpId, vars.priceAfter) : 0;
139
        vars.cdpCollAfter = _cdpId != bytes32(0) ? cdpManager.getCdpCollShares(_cdpId) : 0;
140
        vars.cdpDebtAfter = _cdpId != bytes32(0) ? cdpManager.getCdpDebt(_cdpId) : 0;
141
        vars.liquidatorRewardSharesAfter = _cdpId != bytes32(0)
142
            ? cdpManager.getCdpLiquidatorRewardShares(_cdpId)
143
            : 0;
144
        vars.cdpStatusAfter = _cdpId != bytes32(0) ? cdpManager.getCdpStatus(_cdpId) : 0;
145

                            
                        
146
        vars.isRecoveryModeAfter = cdpManager.checkRecoveryMode(vars.priceAfter); /// @audit This is fine as is because after the system is synched
147
        (vars.feeSplitAfter, , ) = collateral.getPooledEthByShares(cdpManager.DECIMAL_PRECISION()) >
148
            cdpManager.stEthIndex()
149
            ? cdpManager.calcFeeUponStakingReward(
150
                collateral.getPooledEthByShares(cdpManager.DECIMAL_PRECISION()),
151
                cdpManager.stEthIndex()
152
            )
153
            : (0, 0, 0);
154

                            
                        
155
        vars.feeRecipientTotalCollAfter =
156
            activePool.getFeeRecipientClaimableCollShares() +
157
            collateral.sharesOf(activePool.feeRecipientAddress());
158
        vars.feeRecipientCollSharesAfter = activePool.getFeeRecipientClaimableCollShares();
159
        vars.actorCollAfter = collateral.balanceOf(address(actor));
160
        vars.actorEbtcAfter = eBTCToken.balanceOf(address(actor));
161
        vars.actorCdpCountAfter = sortedCdps.cdpCountOf(address(actor));
162
        vars.sortedCdpsSizeAfter = sortedCdps.getSize();
163
        vars.tcrAfter = cdpManager.getTCR(vars.priceAfter);
164
        vars.ebtcTotalSupplyAfter = eBTCToken.totalSupply();
165
        vars.ethPerShareAfter = collateral.getEthPerShare();
166
        vars.activePoolDebtAfter = activePool.getSystemDebt();
167
        vars.activePoolCollAfter = activePool.getSystemCollShares();
168
        vars.collSurplusPoolAfter = collSurplusPool.getTotalSurplusCollShares();
169
        vars.lastGracePeriodStartTimestampAfter = cdpManager.lastGracePeriodStartTimestamp();
170
        vars.lastGracePeriodStartTimestampIsSetAfter =
171
            cdpManager.lastGracePeriodStartTimestamp() != cdpManager.UNSET_TIMESTAMP();
172
        vars.hasGracePeriodPassedAfter =
173
            cdpManager.lastGracePeriodStartTimestamp() != cdpManager.UNSET_TIMESTAMP() &&
174
            block.timestamp >
175
            cdpManager.lastGracePeriodStartTimestamp() +
176
                cdpManager.recoveryModeGracePeriodDuration();
177
        vars.systemDebtRedistributionIndexAfter = cdpManager.systemDebtRedistributionIndex();
178

                            
                        
179
        vars.newTcrAfter = crLens.quoteRealTCR();
180
        vars.newIcrAfter = crLens.quoteRealICR(_cdpId);
181

                            
                        
182
        // Value in system after
183
        vars.valueInSystemAfter = (collateral.getPooledEthByShares(vars.activePoolCollAfter + vars.collSurplusPoolAfter + vars.feeRecipientTotalCollAfter) * vars.priceAfter) / 1e18 - vars.activePoolDebtAfter;
184
    }
185

                            
                        
186
    function _diff() internal view returns (string memory log) {
187
        log = string("\n\t\t\t\tBefore\t\t\tAfter\n");
188
        if (vars.activePoolCollBefore != vars.activePoolCollAfter) {
189
            log = log
190
                .concat("activePoolColl\t\t\t")
191
                .concat(vars.activePoolCollBefore.pretty())
192
                .concat("\t")
193
                .concat(vars.activePoolCollAfter.pretty())
194
                .concat("\n");
195
        }
196
        if (vars.collSurplusPoolBefore != vars.collSurplusPoolAfter) {
197
            log = log
198
                .concat("collSurplusPool\t\t\t")
199
                .concat(vars.collSurplusPoolBefore.pretty())
200
                .concat("\t")
201
                .concat(vars.collSurplusPoolAfter.pretty())
202
                .concat("\n");
203
        }
204
        if (vars.nicrBefore != vars.nicrAfter) {
205
            log = log
206
                .concat("nicr\t\t\t\t")
207
                .concat(vars.nicrBefore.pretty())
208
                .concat("\t")
209
                .concat(vars.nicrAfter.pretty())
210
                .concat("\n");
211
        }
212
        if (vars.icrBefore != vars.icrAfter) {
213
            log = log
214
                .concat("icr\t\t\t\t")
215
                .concat(vars.icrBefore.pretty())
216
                .concat("\t")
217
                .concat(vars.icrAfter.pretty())
218
                .concat("\n");
219
        }
220
        if (vars.newIcrBefore != vars.newIcrAfter) {
221
            log = log
222
                .concat("newIcr\t\t\t\t")
223
                .concat(vars.newIcrBefore.pretty())
224
                .concat("\t")
225
                .concat(vars.newIcrAfter.pretty())
226
                .concat("\n");
227
        }
228
        if (vars.feeSplitBefore != vars.feeSplitAfter) {
229
            log = log
230
                .concat("feeSplit\t\t\t\t")
231
                .concat(vars.feeSplitBefore.pretty())
232
                .concat("\t")
233
                .concat(vars.feeSplitAfter.pretty())
234
                .concat("\n");
235
        }
236
        if (vars.feeRecipientTotalCollBefore != vars.feeRecipientTotalCollAfter) {
237
            log = log
238
                .concat("feeRecipientTotalColl\t")
239
                .concat(vars.feeRecipientTotalCollBefore.pretty())
240
                .concat("\t")
241
                .concat(vars.feeRecipientTotalCollAfter.pretty())
242
                .concat("\n");
243
        }
244
        if (vars.actorCollBefore != vars.actorCollAfter) {
245
            log = log
246
                .concat("actorColl\t\t\t\t")
247
                .concat(vars.actorCollBefore.pretty())
248
                .concat("\t")
249
                .concat(vars.actorCollAfter.pretty())
250
                .concat("\n");
251
        }
252
        if (vars.actorEbtcBefore != vars.actorEbtcAfter) {
253
            log = log
254
                .concat("actorEbtc\t\t\t\t")
255
                .concat(vars.actorEbtcBefore.pretty())
256
                .concat("\t")
257
                .concat(vars.actorEbtcAfter.pretty())
258
                .concat("\n");
259
        }
260
        if (vars.actorCdpCountBefore != vars.actorCdpCountAfter) {
261
            log = log
262
                .concat("actorCdpCount\t\t\t")
263
                .concat(vars.actorCdpCountBefore.pretty())
264
                .concat("\t")
265
                .concat(vars.actorCdpCountAfter.pretty())
266
                .concat("\n");
267
        }
268
        if (vars.cdpCollBefore != vars.cdpCollAfter) {
269
            log = log
270
                .concat("cdpColl\t\t\t\t")
271
                .concat(vars.cdpCollBefore.pretty())
272
                .concat("\t")
273
                .concat(vars.cdpCollAfter.pretty())
274
                .concat("\n");
275
        }
276
        if (vars.cdpDebtBefore != vars.cdpDebtAfter) {
277
            log = log
278
                .concat("cdpDebt\t\t\t\t")
279
                .concat(vars.cdpDebtBefore.pretty())
280
                .concat("\t")
281
                .concat(vars.cdpDebtAfter.pretty())
282
                .concat("\n");
283
        }
284
        if (vars.liquidatorRewardSharesBefore != vars.liquidatorRewardSharesAfter) {
285
            log = log
286
                .concat("liquidatorRewardShares\t\t")
287
                .concat(vars.liquidatorRewardSharesBefore.pretty())
288
                .concat("\t")
289
                .concat(vars.liquidatorRewardSharesAfter.pretty())
290
                .concat("\n");
291
        }
292
        if (vars.sortedCdpsSizeBefore != vars.sortedCdpsSizeAfter) {
293
            log = log
294
                .concat("sortedCdpsSize\t\t\t")
295
                .concat(vars.sortedCdpsSizeBefore.pretty(0))
296
                .concat("\t\t\t")
297
                .concat(vars.sortedCdpsSizeAfter.pretty(0))
298
                .concat("\n");
299
        }
300
        if (vars.cdpStatusBefore != vars.cdpStatusAfter) {
301
            log = log
302
                .concat("cdpStatus\t\t\t")
303
                .concat(vars.cdpStatusBefore.pretty(0))
304
                .concat("\t\t\t")
305
                .concat(vars.cdpStatusAfter.pretty(0))
306
                .concat("\n");
307
        }
308
        if (vars.tcrBefore != vars.tcrAfter) {
309
            log = log
310
                .concat("tcr\t\t\t\t")
311
                .concat(vars.tcrBefore.pretty())
312
                .concat("\t")
313
                .concat(vars.tcrAfter.pretty())
314
                .concat("\n");
315
        }
316
        if (vars.newTcrBefore != vars.newTcrAfter) {
317
            log = log
318
                .concat("newTcr\t\t\t\t")
319
                .concat(vars.newTcrBefore.pretty())
320
                .concat("\t")
321
                .concat(vars.newTcrAfter.pretty())
322
                .concat("\n");
323
        }
324
        if (vars.ebtcTotalSupplyBefore != vars.ebtcTotalSupplyAfter) {
325
            log = log
326
                .concat("ebtcTotalSupply\t\t\t")
327
                .concat(vars.ebtcTotalSupplyBefore.pretty())
328
                .concat("\t")
329
                .concat(vars.ebtcTotalSupplyAfter.pretty())
330
                .concat("\n");
331
        }
332
        if (vars.ethPerShareBefore != vars.ethPerShareAfter) {
333
            log = log
334
                .concat("ethPerShare\t\t\t")
335
                .concat(vars.ethPerShareBefore.pretty())
336
                .concat("\t")
337
                .concat(vars.ethPerShareAfter.pretty())
338
                .concat("\n");
339
        }
340
        if (vars.isRecoveryModeBefore != vars.isRecoveryModeAfter) {
341
            log = log
342
                .concat("isRecoveryMode\t\t\t")
343
                .concat(vars.isRecoveryModeBefore.pretty())
344
                .concat("\t")
345
                .concat(vars.isRecoveryModeAfter.pretty())
346
                .concat("\n");
347
        }
348
        if (vars.lastGracePeriodStartTimestampBefore != vars.lastGracePeriodStartTimestampAfter) {
349
            log = log
350
                .concat("lastGracePeriodStartTimestamp\t")
351
                .concat(vars.lastGracePeriodStartTimestampBefore.pretty())
352
                .concat("\t")
353
                .concat(vars.lastGracePeriodStartTimestampAfter.pretty())
354
                .concat("\n");
355
        }
356
        if (
357
            vars.lastGracePeriodStartTimestampIsSetBefore !=
358
            vars.lastGracePeriodStartTimestampIsSetAfter
359
        ) {
360
            log = log
361
                .concat("lastGracePeriodStartTimestampIsSet\t")
362
                .concat(vars.lastGracePeriodStartTimestampIsSetBefore.pretty())
363
                .concat("\t")
364
                .concat(vars.lastGracePeriodStartTimestampIsSetAfter.pretty())
365
                .concat("\n");
366
        }
367
        if (vars.hasGracePeriodPassedBefore != vars.hasGracePeriodPassedAfter) {
368
            log = log
369
                .concat("hasGracePeriodPassed\t\t")
370
                .concat(vars.hasGracePeriodPassedBefore.pretty())
371
                .concat("\t\t\t")
372
                .concat(vars.hasGracePeriodPassedAfter.pretty())
373
                .concat("\n");
374
        }
375
        if (vars.systemDebtRedistributionIndexBefore != vars.systemDebtRedistributionIndexAfter) {
376
            log = log
377
                .concat("systemDebtRedistributionIndex\t\t")
378
                .concat(vars.systemDebtRedistributionIndexBefore.pretty())
379
                .concat("\t")
380
                .concat(vars.systemDebtRedistributionIndexAfter.pretty())
381
                .concat("\n");
382
        }
383
    }
384
}
385

                            
                        

Lines covered: 174 / 191 (91.1%)

1
pragma solidity 0.8.17;
2

                            
                        
3
import "@crytic/properties/contracts/util/PropertiesConstants.sol";
4

                            
                        
5
import {ICollateralToken} from "../../Dependencies/ICollateralToken.sol";
6
import {EbtcMath} from "../../Dependencies/EbtcMath.sol";
7
import {ActivePool} from "../../ActivePool.sol";
8
import {EBTCToken} from "../../EBTCToken.sol";
9
import {BorrowerOperations} from "../../BorrowerOperations.sol";
10
import {CdpManager} from "../../CdpManager.sol";
11
import {SortedCdps} from "../../SortedCdps.sol";
12
import {Asserts} from "./Asserts.sol";
13
import {CollSurplusPool} from "../../CollSurplusPool.sol";
14
import {PriceFeedTestnet} from "../testnet/PriceFeedTestnet.sol";
15
import {ICdpManagerData} from "../../Interfaces/ICdpManagerData.sol";
16
import {BeforeAfter} from "./BeforeAfter.sol";
17
import {PropertiesDescriptions} from "./PropertiesDescriptions.sol";
18
import {CRLens} from "../../CRLens.sol";
19
import {LiquidationSequencer} from "../../LiquidationSequencer.sol";
20
import {SyncedLiquidationSequencer} from "../../SyncedLiquidationSequencer.sol";
21

                            
                        
22
abstract contract Properties is BeforeAfter, PropertiesDescriptions, Asserts, PropertiesConstants {
23
    function invariant_AP_01(
24
        ICollateralToken collateral,
25
        ActivePool activePool
26
    ) internal view returns (bool) {
27
        return (collateral.sharesOf(address(activePool)) >= activePool.getSystemCollShares());
28
    }
29

                            
                        
30
    function invariant_AP_02(
31
        CdpManager cdpManager,
32
        ActivePool activePool
33
    ) internal view returns (bool) {
34
        return cdpManager.getActiveCdpsCount() > 0 ? activePool.getSystemCollShares() > 0 : true;
35
    }
36

                            
                        
37
    function invariant_AP_03(
38
        EBTCToken eBTCToken,
39
        ActivePool activePool
40
    ) internal view returns (bool) {
41
        return (eBTCToken.totalSupply() == activePool.getSystemDebt());
42
    }
43

                            
                        
44
    function invariant_AP_04(
45
        CdpManager cdpManager,
46
        ActivePool activePool,
47
        uint256 diff_tolerance
48
    ) internal view returns (bool) {
49
        uint256 _cdpCount = cdpManager.getActiveCdpsCount();
50
        uint256 _sum;
51
        for (uint256 i = 0; i < _cdpCount; ++i) {
52
            (, uint256 _coll, ) = cdpManager.getDebtAndCollShares(cdpManager.CdpIds(i));
53
            _sum += _coll;
54
        }
55
        uint256 _activeColl = activePool.getSystemCollShares();
56
        uint256 _diff = _sum > _activeColl ? (_sum - _activeColl) : (_activeColl - _sum);
57
        return (_diff * 1e18 <= diff_tolerance * _activeColl);
58
    }
59

                            
                        
60
    function invariant_AP_05(
61
        CdpManager cdpManager,
62
        uint256 diff_tolerance
63
    ) internal view returns (bool) {
64
        uint256 _cdpCount = cdpManager.getActiveCdpsCount();
65
        uint256 _sum;
66
        for (uint256 i = 0; i < _cdpCount; ++i) {
67
            (uint256 _debt, , ) = cdpManager.getDebtAndCollShares(cdpManager.CdpIds(i));
68
            _sum += _debt;
69
        }
70

                            
                        
71
        bool oldCheck = isApproximateEq(_sum, cdpManager.getSystemDebt(), diff_tolerance);
72
        // New check ensures this is above 1000 wei
73
        bool newCheck = cdpManager.getSystemDebt() - _sum > 1_000;
74
        // @audit We have an instance of getting above 1e18 in rounding error, see `testBrokenInvariantFive`
75
        return oldCheck || !newCheck;
76
    }
77

                            
                        
78
    function invariant_CDPM_01(
79
        CdpManager cdpManager,
80
        SortedCdps sortedCdps
81
    ) internal view returns (bool) {
82
        return (cdpManager.getActiveCdpsCount() == sortedCdps.getSize());
83
    }
84

                            
                        
85
    function invariant_CDPM_02(CdpManager cdpManager) internal view returns (bool) {
86
        uint256 _cdpCount = cdpManager.getActiveCdpsCount();
87
        uint256 _sum;
88
        for (uint256 i = 0; i < _cdpCount; ++i) {
89
            _sum += cdpManager.getCdpStake(cdpManager.CdpIds(i));
90
        }
91
        return (_sum == cdpManager.totalStakes());
92
    }
93

                            
                        
94
    function invariant_CDPM_03(CdpManager cdpManager) internal view returns (bool) {
95
        uint256 _cdpCount = cdpManager.getActiveCdpsCount();
96
        uint256 systemStEthFeePerUnitIndex = cdpManager.systemStEthFeePerUnitIndex();
97
        for (uint256 i = 0; i < _cdpCount; ++i) {
98
            if (
99
                systemStEthFeePerUnitIndex < cdpManager.cdpStEthFeePerUnitIndex(cdpManager.CdpIds(i))
100
            ) {
101
                return false;
102
            }
103
        }
104
        return true;
105
    }
106

                            
                        
107
    /** TODO: See EchidnaToFoundry._getValue */
108
    function invariant_CDPM_04(Vars memory vars) internal view returns (bool) {
109
        return vars.valueInSystemAfter >= vars.valueInSystemBefore || isApproximateEq(vars.valueInSystemAfter, vars.valueInSystemBefore, 0.01e18);
110
    }
111

                            
                        
112
    function invariant_CSP_01(
113
        ICollateralToken collateral,
114
        CollSurplusPool collSurplusPool
115
    ) internal view returns (bool) {
116
        return
117
            collateral.sharesOf(address(collSurplusPool)) >=
118
            collSurplusPool.getTotalSurplusCollShares();
119
    }
120

                            
                        
121
    function invariant_CSP_02(CollSurplusPool collSurplusPool) internal view returns (bool) {
122
        uint256 sum;
123

                            
                        
124
        // NOTE: See PropertiesConstants
125
        // We only have 3 actors so just set these up
126
        sum += collSurplusPool.getSurplusCollShares(address(actors[USER1]));
127
        sum += collSurplusPool.getSurplusCollShares(address(actors[USER2]));
128
        sum += collSurplusPool.getSurplusCollShares(address(actors[USER3]));
129

                            
                        
130
        return sum == collSurplusPool.getTotalSurplusCollShares();
131
    }
132

                            
                        
133
    function invariant_SL_01(CdpManager cdpManager, SortedCdps sortedCdps) internal returns (bool) {
134
        bytes32 currentCdp = sortedCdps.getFirst();
135
        bytes32 nextCdp = sortedCdps.getNext(currentCdp);
136

                            
                        
137
        while (currentCdp != bytes32(0) && nextCdp != bytes32(0) && currentCdp != nextCdp) {
138
            // TODO remove tolerance once proper fix has been applied
139
            uint256 nicrNext = cdpManager.getNominalICR(nextCdp);
140
            uint256 nicrCurrent = cdpManager.getNominalICR(currentCdp);
141
            emit L2(nicrNext, nicrCurrent);
142
            if (nicrNext > nicrCurrent && diffPercent(nicrNext, nicrCurrent) > 0.01e18) {
143
                return false;
144
            }
145

                            
                        
146
            currentCdp = nextCdp;
147
            nextCdp = sortedCdps.getNext(currentCdp);
148
        }
149

                            
                        
150
        return true;
151
    }
152

                            
                        
153
    function invariant_SL_02(
154
        CdpManager cdpManager,
155
        SortedCdps sortedCdps,
156
        PriceFeedTestnet priceFeedMock
157
    ) internal view returns (bool) {
158
        bytes32 _first = sortedCdps.getFirst();
159
        uint256 _price = priceFeedMock.getPrice();
160
        uint256 _firstICR = cdpManager.getICR(_first, _price);
161
        uint256 _TCR = cdpManager.getTCR(_price);
162

                            
                        
163
        if (
164
            _first != sortedCdps.dummyId() &&
165
            _firstICR < _TCR &&
166
            diffPercent(_firstICR, _TCR) > 0.01e18
167
        ) {
168
            return false;
169
        }
170
        return true;
171
    }
172

                            
                        
173
    function invariant_SL_03(
174
        CdpManager cdpManager,
175
        PriceFeedTestnet priceFeedMock,
176
        SortedCdps sortedCdps
177
    ) internal view returns (bool) {
178
        bytes32 currentCdp = sortedCdps.getFirst();
179

                            
                        
180
        uint256 _price = priceFeedMock.getPrice();
181
        if (_price == 0) return true;
182

                            
                        
183
        while (currentCdp != bytes32(0)) {
184
            // Status
185
            if (
186
                ICdpManagerData.Status(cdpManager.getCdpStatus(currentCdp)) !=
187
                ICdpManagerData.Status.active
188
            ) {
189
                return false;
190
            }
191

                            
                        
192
            // Stake > 0
193
            if (cdpManager.getCdpStake(currentCdp) == 0) {
194
                return false;
195
            }
196

                            
                        
197
            currentCdp = sortedCdps.getNext(currentCdp);
198
        }
199
        return true;
200
    }
201

                            
                        
202
    uint256 NICR_ERROR_THRESHOLD = 1e8;
203

                            
                        
204
    function invariant_SL_05(CRLens crLens, SortedCdps sortedCdps) internal returns (bool) {
205
        bytes32 currentCdp = sortedCdps.getFirst();
206

                            
                        
207
        uint256 newIcrPrevious = type(uint256).max;
208

                            
                        
209
        while (currentCdp != bytes32(0)) {
210
            uint256 newIcr = crLens.quoteRealICR(currentCdp);
211
            if (newIcr > newIcrPrevious) {
212
                /// @audit Precision Threshold to flag very scary scenarios
213
                /// Innoquous scenario illustrated here: https://github.com/Badger-Finance/ebtc-fuzz-review/issues/15
214
                if (newIcr - newIcrPrevious > NICR_ERROR_THRESHOLD) {
215
                    return false;
216
                }
217
            }
218
            newIcrPrevious = newIcr;
219

                            
                        
220
            currentCdp = sortedCdps.getNext(currentCdp);
221
        }
222
        return true;
223
    }
224

                            
                        
225
    function invariant_GENERAL_01(Vars memory vars) internal view returns (bool) {
226
        return !vars.isRecoveryModeBefore ? !vars.isRecoveryModeAfter : true;
227
    }
228

                            
                        
229
    function invariant_GENERAL_02(
230
        CdpManager cdpManager,
231
        PriceFeedTestnet priceFeedMock,
232
        EBTCToken eBTCToken
233
    ) internal view returns (bool) {
234
        // TODO how to calculate "the dollar value of eBTC"?
235
        // TODO how do we take into account underlying/shares into this calculation?
236
        return
237
            cdpManager.getTCR(priceFeedMock.getPrice()) > collateral.getPooledEthByShares(1e18)
238
                ? (cdpManager.getSystemCollShares() * priceFeedMock.getPrice()) / 1e18 >=
239
                    eBTCToken.totalSupply()
240
                : (cdpManager.getSystemCollShares() * priceFeedMock.getPrice()) / 1e18 <
241
                    eBTCToken.totalSupply();
242
    }
243

                            
                        
244
    function invariant_GENERAL_03(
245
        CdpManager cdpManager,
246
        BorrowerOperations borrowerOperations,
247
        EBTCToken eBTCToken,
248
        ICollateralToken collateral
249
    ) internal view returns (bool) {
250
        return
251
            collateral.balanceOf(address(cdpManager)) == 0 &&
252
            eBTCToken.balanceOf(address(cdpManager)) == 0 &&
253
            collateral.balanceOf(address(borrowerOperations)) == 0 &&
254
            eBTCToken.balanceOf(address(borrowerOperations)) == 0;
255
    }
256

                            
                        
257
    function invariant_GENERAL_05(
258
        ActivePool activePool,
259
        CdpManager cdpManager,
260
        ICollateralToken collateral
261
    ) internal view returns (bool) {
262
        uint256 totalStipendShares;
263

                            
                        
264
        // Iterate over CDPs add the stipendShares
265
        bytes32 currentCdp = sortedCdps.getFirst();
266
        while (currentCdp != bytes32(0)) {
267
            totalStipendShares += cdpManager.getCdpLiquidatorRewardShares(currentCdp);
268

                            
                        
269
            currentCdp = sortedCdps.getNext(currentCdp);
270
        }
271

                            
                        
272
        return
273
            collateral.sharesOf(address(activePool)) >=
274
            (activePool.getSystemCollShares() +
275
                activePool.getFeeRecipientClaimableCollShares() +
276
                totalStipendShares);
277
    }
278

                            
                        
279
    function invariant_GENERAL_05_B(
280
        CollSurplusPool surplusPool,
281
        ICollateralToken collateral
282
    ) internal view returns (bool) {
283
        return
284
            collateral.sharesOf(address(surplusPool)) >= (surplusPool.getTotalSurplusCollShares());
285
    }
286

                            
                        
287
    function invariant_GENERAL_06(
288
        EBTCToken eBTCToken,
289
        CdpManager cdpManager,
290
        SortedCdps sortedCdps
291
    ) internal view returns (bool) {
292
        uint256 totalSupply = eBTCToken.totalSupply();
293

                            
                        
294
        bytes32 currentCdp = sortedCdps.getFirst();
295
        uint256 cdpsBalance;
296
        while (currentCdp != bytes32(0)) {
297
            (uint256 entireDebt, , ) = cdpManager.getDebtAndCollShares(currentCdp);
298
            cdpsBalance += entireDebt;
299
            currentCdp = sortedCdps.getNext(currentCdp);
300
        }
301

                            
                        
302
        return totalSupply >= cdpsBalance;
303
    }
304

                            
                        
305
    function invariant_GENERAL_08(
306
        CdpManager cdpManager,
307
        SortedCdps sortedCdps,
308
        PriceFeedTestnet priceFeedTestnet,
309
        ICollateralToken collateral
310
    ) internal view returns (bool) {
311
        uint256 curentPrice = priceFeedTestnet.getPrice();
312

                            
                        
313
        bytes32 currentCdp = sortedCdps.getFirst();
314

                            
                        
315
        uint256 sumOfColl;
316
        uint256 sumOfDebt;
317
        while (currentCdp != bytes32(0)) {
318
            uint256 entireColl = cdpManager.getSyncedCdpCollShares(currentCdp);
319
            uint256 entireDebt = cdpManager.getSyncedCdpDebt(currentCdp);
320
            sumOfColl += entireColl;
321
            sumOfDebt += entireDebt;
322
            currentCdp = sortedCdps.getNext(currentCdp);
323
        }
324

                            
                        
325
        uint256 tcrFromSystem = cdpManager.getSyncedTCR(curentPrice);
326

                            
                        
327
        uint256 tcrFromSums = EbtcMath._computeCR(
328
            collateral.getPooledEthByShares(sumOfColl),
329
            sumOfDebt,
330
            curentPrice
331
        );
332
        /// @audit 1e8 precision
333
        return isApproximateEq(tcrFromSystem, tcrFromSums, 1e8); // Up to 1e8 precision is accepted
334
    }
335

                            
                        
336
    function invariant_GENERAL_09(
337
        CdpManager cdpManager,
338
        Vars memory vars
339
    ) internal view returns (bool) {
340
        if (vars.isRecoveryModeBefore) {
341
            if (vars.cdpDebtAfter > vars.cdpDebtBefore) return (vars.icrAfter > cdpManager.MCR());
342
            else return true;
343
        } else {
344
            return (vars.icrAfter > cdpManager.MCR());
345
        }
346
    }
347

                            
                        
348
    function invariant_GENERAL_12(
349
        CdpManager cdpManager,
350
        PriceFeedTestnet priceFeedMock,
351
        CRLens crLens
352
    ) internal returns (bool) {
353
        uint256 curentPrice = priceFeedMock.getPrice();
354
        return crLens.quoteRealTCR() == cdpManager.getSyncedTCR(curentPrice);
355
    }
356

                            
                        
357
    function invariant_GENERAL_13(
358
        CRLens crLens,
359
        CdpManager cdpManager,
360
        PriceFeedTestnet priceFeedMock,
361
        SortedCdps sortedCdps
362
    ) internal returns (bool) {
363
        bytes32 currentCdp = sortedCdps.getFirst();
364

                            
                        
365
        uint256 _price = priceFeedMock.getPrice();
366

                            
                        
367
        // Compare synched with quote for all Cdps
368
        while (currentCdp != bytes32(0)) {
369
            uint256 newIcr = crLens.quoteRealICR(currentCdp);
370
            uint256 synchedICR = cdpManager.getSyncedICR(currentCdp, _price);
371

                            
                        
372
            if (newIcr != synchedICR) {
373
                return false;
374
            }
375

                            
                        
376
            currentCdp = sortedCdps.getNext(currentCdp);
377
        }
378
        return true;
379
    }
380

                            
                        
381
    function invariant_GENERAL_14(
382
        CRLens crLens,
383
        CdpManager cdpManager,
384
        SortedCdps sortedCdps
385
    ) internal returns (bool) {
386
        bytes32 currentCdp = sortedCdps.getFirst();
387

                            
                        
388
        uint256 newIcrPrevious = type(uint256).max;
389

                            
                        
390
        // Compare synched with quote for all Cdps
391
        while (currentCdp != bytes32(0)) {
392
            uint256 newNICR = crLens.quoteRealNICR(currentCdp);
393
            uint256 synchedNICR = cdpManager.getSyncedNominalICR(currentCdp); // Uses cached stETH index -> It's not the "real NICR"
394

                            
                        
395
            if (newNICR != synchedNICR) {
396
                return false;
397
            }
398

                            
                        
399
            currentCdp = sortedCdps.getNext(currentCdp);
400
        }
401
        return true;
402
    }
403

                            
                        
404
    function invariant_GENERAL_15() internal returns (bool) {
405
        return crLens.quoteAnything(simulator.simulateRepayEverythingAndCloseCdps) == simulator.TRUE();
406
    }
407

                            
                        
408
    function invariant_LS_01(
409
        CdpManager cdpManager,
410
        LiquidationSequencer ls,
411
        SyncedLiquidationSequencer syncedLs,
412
        PriceFeedTestnet priceFeedTestnet
413
    ) internal returns (bool) {
414
        // Or just compare max lenght since that's the one with all of them
415
        uint256 n = cdpManager.getActiveCdpsCount();
416

                            
                        
417
        // Get
418
        uint256 price = priceFeedTestnet.getPrice();
419

                            
                        
420
        // Get lists
421
        bytes32[] memory cdpsFromCurrent = ls.sequenceLiqToBatchLiqWithPrice(n, price);
422
        bytes32[] memory cdpsSynced = syncedLs.sequenceLiqToBatchLiqWithPrice(n, price);
423

                            
                        
424
        uint256 length = cdpsFromCurrent.length;
425
        if (length != cdpsSynced.length) {
426
            return false;
427
        }
428

                            
                        
429
        // Compare Lists
430
        for (uint256 i; i < length; i++) {
431
            // Find difference = broken
432
            if (cdpsFromCurrent[i] != cdpsSynced[i]) {
433
                return false;
434
            }
435
        }
436

                            
                        
437
        // Implies we're good
438
        return true;
439
    }
440

                            
                        
441
    function invariant_DUMMY_01(PriceFeedTestnet priceFeedTestnet) internal view returns (bool) {
442
        return priceFeedTestnet.getPrice() > 0;
443
    }
444
}
445

                            
                        

Lines covered: 0 / 0 (0.0%)

1
pragma solidity 0.8.17;
2

                            
                        
3
abstract contract PropertiesDescriptions {
4
    ///////////////////////////////////////////////////////
5
    // Active Pool
6
    ///////////////////////////////////////////////////////
7

                            
                        
8
    string constant AP_01 =
9
        "AP-01: The collateral balance in the active pool is greater than or equal to its accounting number";
10
    string constant AP_02 =
11
        "AP-06: The collateral balance of the ActivePool is positive if there is at least one CDP open";
12
    string constant AP_03 =
13
        "AP-03: The eBTC debt accounting number in active pool is greater than or equal to its accounting number";
14
    string constant AP_04 =
15
        "AP-04: The total collateral in active pool should be equal to the sum of all individual CDP collateral";
16
    string constant AP_05 =
17
        "AP-05: The sum of debt accounting in active pool should be equal to sum of debt accounting of individual CDPs";
18

                            
                        
19
    ///////////////////////////////////////////////////////
20
    // Cdp Manager
21
    ///////////////////////////////////////////////////////
22

                            
                        
23
    string constant CDPM_01 =
24
        "CDPM-01: The count of active CDPs is equal to the SortedCdp list length";
25
    string constant CDPM_02 = "CDPM-02: The sum of active CDPs stake is equal to totalStakes";
26
    string constant CDPM_03 =
27
        "CDPM-03: The stFeePerUnit tracker for individual CDP is equal to or less than the global variable";
28
    string constant CDPM_04 = "CDPM-04: The total system value does not decrease during redemptions";
29
    string constant CDPM_05 = "CDPM-05: Redemptions do not increase the total system debt";
30
    string constant CDPM_06 = "CDPM-06: Redemptions do not increase the total system debt";
31

                            
                        
32
    ///////////////////////////////////////////////////////
33
    // Collateral Surplus Pool
34
    ///////////////////////////////////////////////////////
35

                            
                        
36
    string constant CSP_01 =
37
        "CSP-01: The collateral balance in the collSurplus pool is greater than or equal to its accounting number";
38

                            
                        
39
    ///////////////////////////////////////////////////////
40
    // Sorted List
41
    ///////////////////////////////////////////////////////
42

                            
                        
43
    string constant SL_01 =
44
        "SL-01: The NICR ranking in the sorted list should follow descending order";
45
    string constant SL_02 =
46
        "SL-02: The the first(highest) ICR in the sorted list should be greater or equal to TCR (with tolerance due to rounding errors)";
47
    string constant SL_03 = "SL-03: All CDPs have status active and stake greater than zero";
48
    string constant SL_05 =
49
        "SL-05: The CDPs should be sorted in descending order of new ICR (accrued)";
50

                            
                        
51
    ///////////////////////////////////////////////////////
52
    // Borrower Operations
53
    ///////////////////////////////////////////////////////
54

                            
                        
55
    string constant BO_01 = "BO-01: Users can only open CDPs with healthy ICR";
56
    string constant BO_02 = "BO-02: Users must repay all debt to close a CDP";
57
    string constant BO_03 = "BO-03: Adding collateral doesn't reduce Nominal ICR";
58
    string constant BO_04 = "BO-04: Removing collateral does not increase the Nominal ICR";
59
    string constant BO_05 =
60
        "BO-05: When a borrower closes their active CDP, the gas compensation is refunded to the user";
61
    string constant BO_07 = "BO-07: eBTC tokens are burned upon repayment of a CDP's debt";
62
    string constant BO_08 = "BO-08: TCR must increase after a repayment";
63

                            
                        
64
    ///////////////////////////////////////////////////////
65
    // General
66
    ///////////////////////////////////////////////////////
67
    string constant GENERAL_01 =
68
        "GENERAL-01: After any operation, the system should not enter in Recovery Mode";
69
    string constant GENERAL_02 =
70
        "GENERAL-02: The dollar value of the locked stETH exceeds the dollar value of the issued eBTC if TCR is greater than 100%";
71
    string constant GENERAL_03 =
72
        "GENERAL-03: CdpManager and BorrowerOperations do not hold value terms of stETH and eBTC unless there are donations";
73
    string constant GENERAL_05 =
74
        "GENERAL-05: At all times, the total stETH shares of the system exceeds the deposits if there is no negative rebasing events";
75
    string constant GENERAL_06 =
76
        "GENERAL-06: At all times, the total debt is greater than the sum of all debts from all CDPs";
77
    string constant GENERAL_08 =
78
        "GENERAL-08: At all times TCR = SUM(COLL)  * price / SUM(DEBT) of all CDPs";
79
    string constant GENERAL_09 =
80
        "GENERAL-09: After any operation, the ICR of a CDP must be above the MCR in Normal Mode, and after debt increase in Recovery Mode the ICR must be above the CCR";
81
    string constant GENERAL_10 = "GENERAL-10: All CDPs should maintain a minimum collateral size";
82
    string constant GENERAL_11 =
83
        "GENERAL-11: The TCR pre-computed (TCRNotified) is the same as the one after all calls";
84
    string constant GENERAL_12 =
85
        "GENERAL-12: The synchedTCR matches the TCR after accrual (as returned by CrLens)";
86
    string constant GENERAL_13 =
87
        "GENERAL-13: The SynchedICR of every CDP in the Linked List Matches the ICR the CDPs will have the call (as returned by CrLens)";
88
    string constant GENERAL_14 =
89
        "GENERAL-14: The NominalICR from `getNominalICR` matches `quoteRealNICR` (as returned by CrLens)";
90

                            
                        
91
    ///////////////////////////////////////////////////////
92
    // Redemptions
93
    ///////////////////////////////////////////////////////
94

                            
                        
95
    string constant R_07 = "R-07: TCR should not decrease after redemptions";
96
    string constant R_08 = "R-08: The user eBTC balance should be used to pay the system debt";
97

                            
                        
98
    ///////////////////////////////////////////////////////
99
    // Liquidations
100
    ///////////////////////////////////////////////////////
101

                            
                        
102
    string constant L_01 =
103
        "L-01: Liquidation only succeeds if ICR < 110% in Normal Mode, or if ICR < 125% in Recovery Mode";
104
    string constant L_09 =
105
        "L-09: Undercollateralized liquidations are also incentivized with the Gas Stipend";
106
    string constant L_12 = "L-12: TCR must increase after liquidation with no redistributions";
107
    string constant L_14 =
108
        "If the RM grace period is set and we're in recovery mode, new actions that keep the system in recovery mode should not change the cooldown timestamp";
109
    string constant L_15 =
110
        "L-15: The RM grace period should set if a BO/liquidation/redistribution makes the TCR above CCR";
111
    string constant L_16 =
112
        "L-16: The RM grace period should reset if a BO/liquidation/redistribution makes the TCR below CCR";
113

                            
                        
114
    ///////////////////////////////////////////////////////
115
    // eBTC
116
    ///////////////////////////////////////////////////////
117

                            
                        
118
    string constant EBTC_02 =
119
        "EBTC-02: Any eBTC holder (whether or not they have an active CDP) may redeem their eBTC unless TCR is below MCR";
120

                            
                        
121
    ///////////////////////////////////////////////////////
122
    // Fee Recipient
123
    ///////////////////////////////////////////////////////
124

                            
                        
125
    string constant F_01 = "F-01: `claimFeeRecipientCollShares` allows to claim at any time";
126
    string constant F_02 = "F-02: Fees From Redemptions are added to `claimFeeRecipientCollShares`";
127
    string constant F_03 = "F-03: Fees From FlashLoans are sent to the fee Recipient";
128
    ///////////////////////////////////////////////////////
129
    // Price Feed
130
    ///////////////////////////////////////////////////////
131

                            
                        
132
    string constant PF_01 = "PF-01: The price feed must never revert";
133
    string constant PF_02 = "PF-02: The price feed must follow valid status transitions";
134
    string constant PF_03 = "PF-03: The price feed must never deadlock";
135
    string constant PF_04 =
136
        "PF-04: The price feed should never report an outdated price if chainlink is Working";
137
    string constant PF_05 =
138
        "PF-05: The price feed should never use the fallback if chainlink is Working";
139
    string constant PF_06 = "PF-06: The system never tries to use the fallback if it is not set";
140
}
141

                            
                        

Lines covered: 20 / 20 (100.0%)

1
pragma solidity 0.8.17;
2

                            
                        
3
import {BorrowerOperations} from "../../BorrowerOperations.sol";
4
import {CdpManager} from "../../CdpManager.sol";
5
import {SortedCdps} from "../../SortedCdps.sol";
6
import {Actor} from "./Actor.sol";
7

                            
                        
8
contract Simulator {
9
    uint256 public constant TRUE = uint256(keccak256(abi.encodePacked("TRUE")));
10
    event Log(string);
11

                            
                        
12
    Actor[] private actors;
13
    CdpManager private cdpManager;
14
    SortedCdps private sortedCdps;
15
    BorrowerOperations private borrowerOperations;
16

                            
                        
17
    constructor(
18
        Actor[] memory _actors,
19
        CdpManager _cdpManager,
20
        SortedCdps _sortedCdps,
21
        BorrowerOperations _borrowerOperations
22
    ) {
23
        actors = _actors;
24
        cdpManager = _cdpManager;
25
        sortedCdps = _sortedCdps;
26
        borrowerOperations = _borrowerOperations;
27
    }
28

                            
                        
29
    function simulateRepayEverythingAndCloseCdps() external {
30
        bool success;
31

                            
                        
32
        bytes32 currentCdp = sortedCdps.getFirst();
33
        while (currentCdp != bytes32(0) && sortedCdps.getSize() > 1) {
34
            Actor actor = Actor(payable(sortedCdps.getOwnerAddress(currentCdp)));
35
            (uint256 entireDebt, , ) = cdpManager.getDebtAndCollShares(currentCdp);
36

                            
                        
37
            (success, ) = actor.proxy(
38
                address(borrowerOperations),
39
                abi.encodeWithSelector(BorrowerOperations.closeCdp.selector, currentCdp)
40
            );
41
            require(success);
42

                            
                        
43
            currentCdp = sortedCdps.getNext(currentCdp);
44
        }
45

                            
                        
46
        _success();
47
    }
48

                            
                        
49
    function _success() private {
50
        uint256 ans = TRUE;
51
        assembly {
52
            let ptr := mload(0x40)
53
            mstore(ptr, ans)
54
            revert(ptr, 32)
55
        }
56
    }
57
}
58

                            
                        

Lines covered: 202 / 202 (100.0%)

1
pragma solidity 0.8.17;
2

                            
                        
3
import "@crytic/properties/contracts/util/PropertiesConstants.sol";
4

                            
                        
5
import "../../Interfaces/ICdpManagerData.sol";
6
import "../../Dependencies/SafeMath.sol";
7
import "../../CdpManager.sol";
8
import "../../LiquidationLibrary.sol";
9
import "../../BorrowerOperations.sol";
10
import "../../ActivePool.sol";
11
import "../../CollSurplusPool.sol";
12
import "../../SortedCdps.sol";
13
import "../../HintHelpers.sol";
14
import "../../FeeRecipient.sol";
15
import "../testnet/PriceFeedTestnet.sol";
16
import "../CollateralTokenTester.sol";
17
import "../EBTCTokenTester.sol";
18
import "../../Governor.sol";
19
import "../../EBTCDeployer.sol";
20

                            
                        
21
import "./Properties.sol";
22
import "./Actor.sol";
23
import "../BaseStorageVariables.sol";
24

                            
                        
25
abstract contract TargetContractSetup is BaseStorageVariables, PropertiesConstants {
26
    using SafeMath for uint;
27

                            
                        
28
    bytes4 internal constant BURN_SIG = bytes4(keccak256(bytes("burn(address,uint256)")));
29

                            
                        
30
    uint internal numberOfCdps;
31

                            
                        
32
    struct CDPChange {
33
        uint collAddition;
34
        uint collReduction;
35
        uint debtAddition;
36
        uint debtReduction;
37
    }
38

                            
                        
39
    function _setUp() internal {
40
        defaultGovernance = address(this);
41
        ebtcDeployer = new EBTCDeployer();
42

                            
                        
43
        // Default governance is deployer
44
        // vm.prank(defaultGovernance);
45
        collateral = new CollateralTokenTester();
46

                            
                        
47
        EBTCDeployer.EbtcAddresses memory addr = ebtcDeployer.getFutureEbtcAddresses();
48

                            
                        
49
        {
50
            bytes memory creationCode;
51
            bytes memory args;
52

                            
                        
53
            // Use EBTCDeployer to deploy all contracts at determistic addresses
54

                            
                        
55
            // Authority
56
            creationCode = type(Governor).creationCode;
57
            args = abi.encode(address(this));
58

                            
                        
59
            authority = Governor(
60
                ebtcDeployer.deploy(ebtcDeployer.AUTHORITY(), abi.encodePacked(creationCode, args))
61
            );
62

                            
                        
63
            // Liquidation Library
64
            creationCode = type(LiquidationLibrary).creationCode;
65
            args = abi.encode(
66
                addr.borrowerOperationsAddress,
67
                addr.collSurplusPoolAddress,
68
                addr.ebtcTokenAddress,
69
                addr.sortedCdpsAddress,
70
                addr.activePoolAddress,
71
                addr.priceFeedAddress,
72
                address(collateral)
73
            );
74

                            
                        
75
            liqudationLibrary = LiquidationLibrary(
76
                ebtcDeployer.deploy(
77
                    ebtcDeployer.LIQUIDATION_LIBRARY(),
78
                    abi.encodePacked(creationCode, args)
79
                )
80
            );
81

                            
                        
82
            // CDP Manager
83
            creationCode = type(CdpManager).creationCode;
84
            args = abi.encode(
85
                addr.liquidationLibraryAddress,
86
                addr.authorityAddress,
87
                addr.borrowerOperationsAddress,
88
                addr.collSurplusPoolAddress,
89
                addr.ebtcTokenAddress,
90
                addr.sortedCdpsAddress,
91
                addr.activePoolAddress,
92
                addr.priceFeedAddress,
93
                address(collateral)
94
            );
95

                            
                        
96
            cdpManager = CdpManager(
97
                ebtcDeployer.deploy(ebtcDeployer.CDP_MANAGER(), abi.encodePacked(creationCode, args))
98
            );
99

                            
                        
100
            // Borrower Operations
101
            creationCode = type(BorrowerOperations).creationCode;
102
            args = abi.encode(
103
                addr.cdpManagerAddress,
104
                addr.activePoolAddress,
105
                addr.collSurplusPoolAddress,
106
                addr.priceFeedAddress,
107
                addr.sortedCdpsAddress,
108
                addr.ebtcTokenAddress,
109
                addr.feeRecipientAddress,
110
                address(collateral)
111
            );
112

                            
                        
113
            borrowerOperations = BorrowerOperations(
114
                ebtcDeployer.deploy(
115
                    ebtcDeployer.BORROWER_OPERATIONS(),
116
                    abi.encodePacked(creationCode, args)
117
                )
118
            );
119

                            
                        
120
            // Price Feed Mock
121
            creationCode = type(PriceFeedTestnet).creationCode;
122
            args = abi.encode(addr.authorityAddress);
123

                            
                        
124
            priceFeedMock = PriceFeedTestnet(
125
                ebtcDeployer.deploy(ebtcDeployer.PRICE_FEED(), abi.encodePacked(creationCode, args))
126
            );
127

                            
                        
128
            // Sorted CDPS
129
            creationCode = type(SortedCdps).creationCode;
130
            args = abi.encode(
131
                type(uint256).max,
132
                addr.cdpManagerAddress,
133
                addr.borrowerOperationsAddress
134
            );
135

                            
                        
136
            sortedCdps = SortedCdps(
137
                ebtcDeployer.deploy(ebtcDeployer.SORTED_CDPS(), abi.encodePacked(creationCode, args))
138
            );
139

                            
                        
140
            // Active Pool
141
            creationCode = type(ActivePool).creationCode;
142
            args = abi.encode(
143
                addr.borrowerOperationsAddress,
144
                addr.cdpManagerAddress,
145
                address(collateral),
146
                addr.collSurplusPoolAddress,
147
                addr.feeRecipientAddress
148
            );
149

                            
                        
150
            activePool = ActivePool(
151
                ebtcDeployer.deploy(ebtcDeployer.ACTIVE_POOL(), abi.encodePacked(creationCode, args))
152
            );
153

                            
                        
154
            // Coll Surplus Pool
155
            creationCode = type(CollSurplusPool).creationCode;
156
            args = abi.encode(
157
                addr.borrowerOperationsAddress,
158
                addr.cdpManagerAddress,
159
                addr.activePoolAddress,
160
                address(collateral)
161
            );
162

                            
                        
163
            collSurplusPool = CollSurplusPool(
164
                ebtcDeployer.deploy(
165
                    ebtcDeployer.COLL_SURPLUS_POOL(),
166
                    abi.encodePacked(creationCode, args)
167
                )
168
            );
169

                            
                        
170
            // Hint Helpers
171
            creationCode = type(HintHelpers).creationCode;
172
            args = abi.encode(
173
                addr.sortedCdpsAddress,
174
                addr.cdpManagerAddress,
175
                address(collateral),
176
                addr.activePoolAddress,
177
                addr.priceFeedAddress
178
            );
179

                            
                        
180
            hintHelpers = HintHelpers(
181
                ebtcDeployer.deploy(
182
                    ebtcDeployer.HINT_HELPERS(),
183
                    abi.encodePacked(creationCode, args)
184
                )
185
            );
186

                            
                        
187
            // eBTC Token
188
            creationCode = type(EBTCTokenTester).creationCode;
189
            args = abi.encode(
190
                addr.cdpManagerAddress,
191
                addr.borrowerOperationsAddress,
192
                addr.authorityAddress
193
            );
194

                            
                        
195
            eBTCToken = EBTCTokenTester(
196
                ebtcDeployer.deploy(ebtcDeployer.EBTC_TOKEN(), abi.encodePacked(creationCode, args))
197
            );
198

                            
                        
199
            // Fee Recipieint
200
            creationCode = type(FeeRecipient).creationCode;
201
            args = abi.encode(
202
                addr.ebtcTokenAddress,
203
                addr.cdpManagerAddress,
204
                addr.borrowerOperationsAddress,
205
                addr.activePoolAddress,
206
                address(collateral)
207
            );
208

                            
                        
209
            feeRecipient = FeeRecipient(
210
                ebtcDeployer.deploy(
211
                    ebtcDeployer.FEE_RECIPIENT(),
212
                    abi.encodePacked(creationCode, args)
213
                )
214
            );
215

                            
                        
216
            // Configure authority
217
            authority.setRoleName(0, "Admin");
218
            authority.setRoleName(1, "eBTCToken: mint");
219
            authority.setRoleName(2, "eBTCToken: burn");
220
            authority.setRoleName(3, "CDPManager: all");
221
            authority.setRoleName(4, "PriceFeed: setTellorCaller");
222
            authority.setRoleName(5, "BorrowerOperations: all");
223

                            
                        
224
            authority.setRoleCapability(1, address(eBTCToken), eBTCToken.mint.selector, true);
225

                            
                        
226
            authority.setRoleCapability(2, address(eBTCToken), BURN_SIG, true);
227

                            
                        
228
            authority.setRoleCapability(
229
                3,
230
                address(cdpManager),
231
                cdpManager.setStakingRewardSplit.selector,
232
                true
233
            );
234
            authority.setRoleCapability(
235
                3,
236
                address(cdpManager),
237
                cdpManager.setRedemptionFeeFloor.selector,
238
                true
239
            );
240
            authority.setRoleCapability(
241
                3,
242
                address(cdpManager),
243
                cdpManager.setMinuteDecayFactor.selector,
244
                true
245
            );
246
            authority.setRoleCapability(3, address(cdpManager), cdpManager.setBeta.selector, true);
247
            authority.setRoleCapability(
248
                3,
249
                address(cdpManager),
250
                cdpManager.setGracePeriod.selector,
251
                true
252
            );
253
            authority.setRoleCapability(
254
                3,
255
                address(cdpManager),
256
                cdpManager.setRedemptionsPaused.selector,
257
                true
258
            );
259

                            
                        
260
            authority.setRoleCapability(
261
                4,
262
                address(priceFeedMock),
263
                priceFeedMock.setFallbackCaller.selector,
264
                true
265
            );
266

                            
                        
267
            authority.setRoleCapability(
268
                5,
269
                address(borrowerOperations),
270
                borrowerOperations.setFeeBps.selector,
271
                true
272
            );
273
            authority.setRoleCapability(
274
                5,
275
                address(borrowerOperations),
276
                borrowerOperations.setFlashLoansPaused.selector,
277
                true
278
            );
279

                            
                        
280
            authority.setRoleCapability(5, address(activePool), activePool.setFeeBps.selector, true);
281
            authority.setRoleCapability(
282
                5,
283
                address(activePool),
284
                activePool.setFlashLoansPaused.selector,
285
                true
286
            );
287
            authority.setRoleCapability(
288
                5,
289
                address(activePool),
290
                activePool.claimFeeRecipientCollShares.selector,
291
                true
292
            );
293

                            
                        
294
            authority.setUserRole(defaultGovernance, 0, true);
295
            authority.setUserRole(defaultGovernance, 1, true);
296
            authority.setUserRole(defaultGovernance, 2, true);
297
            authority.setUserRole(defaultGovernance, 3, true);
298
            authority.setUserRole(defaultGovernance, 4, true);
299
            authority.setUserRole(defaultGovernance, 5, true);
300

                            
                        
301
            crLens = new CRLens(address(cdpManager), address(priceFeedMock));
302

                            
                        
303
            liquidationSequencer = new LiquidationSequencer(
304
                address(cdpManager),
305
                address(cdpManager.sortedCdps()),
306
                address(priceFeedMock),
307
                address(activePool),
308
                address(collateral)
309
            );
310
            syncedLiquidationSequencer = new SyncedLiquidationSequencer(
311
                address(cdpManager),
312
                address(cdpManager.sortedCdps()),
313
                address(priceFeedMock),
314
                address(activePool),
315
                address(collateral)
316
            );
317
        }
318
    }
319

                            
                        
320
    event Log(string);
321

                            
                        
322
    function _setUpFork() internal {
323
        defaultGovernance = address(0xA967Ba66Fb284EC18bbe59f65bcf42dD11BA8128);
324
        ebtcDeployer = EBTCDeployer(0xe90f99c08F286c48db4D1AfdAE6C122de69B7219);
325
        collateral = CollateralTokenTester(payable(0xf8017430A0efE03577f6aF88069a21900448A373));
326
        {
327
            authority = Governor(0x4945Fc25282b1bC103d2C62C251Cd022138c1de9);
328
            liqudationLibrary = LiquidationLibrary(0xE8943a17363DE9A6e0d4A5d48d5Ab45283199F77);
329
            cdpManager = CdpManager(0x0c5C2B93b96C9B3aD7fb9915952BD7BA256C4f04);
330
            borrowerOperations = BorrowerOperations(0xA178BFBc42E3D886d540CDDcf4562c53a8Fc02c1);
331
            priceFeedMock = PriceFeedTestnet(0x5C819E5D61EFCfBd7e4635f1112f3bF94663999b);
332
            sortedCdps = SortedCdps(0xDeFF25eC3cd3041BC8B9A464F9BEc12EB8247Be6);
333
            activePool = ActivePool(0x55abdfb760dd032627D531f7cF3DAa72549CEbA2);
334
            collSurplusPool = CollSurplusPool(0x7b4D951D7b8090f62bD009b371abd7Fe04aB7e1A);
335
            hintHelpers = HintHelpers(0xCaBdBc4218dd4b9E3fB9842232aD0aFc7c431693);
336
            eBTCToken = EBTCTokenTester(0x9Aa69Db8c53E504EF22615390EE9Eb72cb8bE498);
337
            feeRecipient = FeeRecipient(0x40FF68eaE525233950B63C2BCEa39770efDE52A4);
338

                            
                        
339
            crLens = new CRLens(address(cdpManager), address(priceFeedMock));
340

                            
                        
341
            liquidationSequencer = new LiquidationSequencer(
342
                address(cdpManager),
343
                address(cdpManager.sortedCdps()),
344
                address(priceFeedMock),
345
                address(activePool),
346
                address(collateral)
347
            );
348
        }
349
    }
350

                            
                        
351
    function _setUpActors() internal {
352
        bool success;
353
        address[] memory tokens = new address[](2);
354
        tokens[0] = address(eBTCToken);
355
        tokens[1] = address(collateral);
356
        address[] memory callers = new address[](2);
357
        callers[0] = address(borrowerOperations);
358
        callers[1] = address(activePool);
359
        address[] memory addresses = new address[](3);
360
        addresses[0] = USER1;
361
        addresses[1] = USER2;
362
        addresses[2] = USER3;
363
        Actor[] memory actorsArray = new Actor[](NUMBER_OF_ACTORS);
364
        for (uint i = 0; i < NUMBER_OF_ACTORS; i++) {
365
            actors[addresses[i]] = new Actor(tokens, callers);
366
            (success, ) = address(actors[addresses[i]]).call{value: INITIAL_ETH_BALANCE}("");
367
            assert(success);
368
            (success, ) = actors[addresses[i]].proxy(
369
                address(collateral),
370
                abi.encodeWithSelector(CollateralTokenTester.deposit.selector, ""),
371
                INITIAL_COLL_BALANCE
372
            );
373
            assert(success);
374
            actorsArray[i] = actors[addresses[i]];
375
        }
376
        simulator = new Simulator(actorsArray, cdpManager, sortedCdps, borrowerOperations);
377
    }
378

                            
                        
379
    function _openWhaleCdpAndTransferEBTC() internal {
380
        bool success;
381
        Actor actor = actors[USER3]; // USER3 is the whale CDP holder
382
        uint256 _col = INITIAL_COLL_BALANCE / 2; // 50% of their initial collateral balance
383

                            
                        
384
        uint256 price = priceFeedMock.getPrice();
385
        uint256 _EBTCAmount = (_col * price) / cdpManager.CCR();
386

                            
                        
387
        (success, ) = actor.proxy(
388
            address(collateral),
389
            abi.encodeWithSelector(
390
                CollateralTokenTester.approve.selector,
391
                address(borrowerOperations),
392
                _col
393
            )
394
        );
395
        assert(success);
396
        (success, ) = actor.proxy(
397
            address(borrowerOperations),
398
            abi.encodeWithSelector(
399
                BorrowerOperations.openCdp.selector,
400
                _EBTCAmount,
401
                bytes32(0),
402
                bytes32(0),
403
                _col
404
            )
405
        );
406
        assert(success);
407
        address[] memory addresses = new address[](2);
408
        addresses[0] = USER1;
409
        addresses[1] = USER2;
410
        for (uint i = 0; i < addresses.length; i++) {
411
            (success, ) = actor.proxy(
412
                address(eBTCToken),
413
                abi.encodeWithSelector(eBTCToken.transfer.selector, actors[addresses[i]], _EBTCAmount / 3)
414
            );
415
            assert(success);
416
        }
417
    }
418
}
419

                            
                        

Lines covered: 390 / 593 (65.8%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
import "@crytic/properties/contracts/util/Hevm.sol";
6

                            
                        
7
import "../../Interfaces/ICdpManagerData.sol";
8
import "../../Dependencies/SafeMath.sol";
9
import "../../CdpManager.sol";
10
import "../../LiquidationLibrary.sol";
11
import "../../BorrowerOperations.sol";
12
import "../../ActivePool.sol";
13
import "../../CollSurplusPool.sol";
14
import "../../SortedCdps.sol";
15
import "../../HintHelpers.sol";
16
import "../../FeeRecipient.sol";
17
import "../testnet/PriceFeedTestnet.sol";
18
import "../CollateralTokenTester.sol";
19
import "../EBTCTokenTester.sol";
20
import "../../Governor.sol";
21
import "../../EBTCDeployer.sol";
22

                            
                        
23
import "./Properties.sol";
24
import "./Actor.sol";
25
import "./BeforeAfter.sol";
26
import "./TargetContractSetup.sol";
27
import "./Asserts.sol";
28
import "../BaseStorageVariables.sol";
29

                            
                        
30
abstract contract TargetFunctions is Properties {
31
    modifier setup() virtual {
32
        actor = actors[msg.sender];
33
        _;
34
    }
35

                            
                        
36
    ///////////////////////////////////////////////////////
37
    // Helper functions
38
    ///////////////////////////////////////////////////////
39

                            
                        
40
    function _totalCdpsBelowMcr() internal returns (uint256) {
41
        uint256 ans;
42
        bytes32 currentCdp = sortedCdps.getFirst();
43

                            
                        
44
        uint256 _price = priceFeedMock.getPrice();
45

                            
                        
46
        while (currentCdp != bytes32(0)) {
47
            if (cdpManager.getICR(currentCdp, _price) < cdpManager.MCR()) {
48
                ++ans;
49
            }
50

                            
                        
51
            currentCdp = sortedCdps.getNext(currentCdp);
52
        }
53

                            
                        
54
        return ans;
55
    }
56

                            
                        
57
    function _getCdpIdsAndICRs() internal view returns (Cdp[] memory ans) {
58
        ans = new Cdp[](sortedCdps.getSize());
59
        uint256 i = 0;
60
        bytes32 currentCdp = sortedCdps.getFirst();
61

                            
                        
62
        uint256 _price = priceFeedMock.getPrice();
63

                            
                        
64
        while (currentCdp != bytes32(0)) {
65
            ans[i++] = Cdp({id: currentCdp, icr: cdpManager.getSyncedICR(currentCdp, _price)}); /// @audit NOTE: Synced to ensure it's realistic
66

                            
                        
67
            currentCdp = sortedCdps.getNext(currentCdp);
68
        }
69
    }
70

                            
                        
71
    function _cdpIdsAndICRsDiff(
72
        Cdp[] memory superset,
73
        Cdp[] memory subset
74
    ) internal returns (Cdp[] memory ans) {
75
        ans = new Cdp[](superset.length - subset.length);
76
        uint256 index = 0;
77
        for (uint256 i = 0; i < superset.length; i++) {
78
            bool duplicate = false;
79
            for (uint256 j = 0; j < subset.length; j++) {
80
                if (superset[i].id == subset[j].id) {
81
                    duplicate = true;
82
                }
83
            }
84
            if (!duplicate) {
85
                ans[index++] = superset[i];
86
            }
87
        }
88
    }
89

                            
                        
90
    function _getRandomCdp(uint _i) internal view returns (bytes32) {
91
        uint _cdpIdx = _i % cdpManager.getActiveCdpsCount();
92
        return cdpManager.CdpIds(_cdpIdx);
93
    }
94

                            
                        
95
    event FlashLoanAction(uint, uint);
96

                            
                        
97
    function _getFlashLoanActions(uint256 value) internal returns (bytes memory) {
98
        uint256 _actions = between(value, 1, MAX_FLASHLOAN_ACTIONS);
99
        uint256 _EBTCAmount = between(value, 1, eBTCToken.totalSupply() / 2);
100
        uint256 _col = between(value, 1, cdpManager.getSystemCollShares() / 2);
101
        uint256 _n = between(value, 1, cdpManager.getActiveCdpsCount());
102

                            
                        
103
        uint256 numberOfCdps = sortedCdps.cdpCountOf(address(actor));
104
        require(numberOfCdps > 0, "Actor must have at least one CDP open");
105
        uint256 _i = between(value, 0, numberOfCdps - 1);
106
        bytes32 _cdpId = sortedCdps.cdpOfOwnerByIndex(address(actor), _i);
107
        t(_cdpId != bytes32(0), "CDP ID must not be null if the index is valid");
108

                            
                        
109
        address[] memory _targets = new address[](_actions);
110
        bytes[] memory _calldatas = new bytes[](_actions);
111

                            
                        
112
        address[] memory _allTargets = new address[](6);
113
        bytes[] memory _allCalldatas = new bytes[](6);
114

                            
                        
115
        _allTargets[0] = address(borrowerOperations);
116
        _allCalldatas[0] = abi.encodeWithSelector(
117
            BorrowerOperations.openCdp.selector,
118
            _EBTCAmount,
119
            bytes32(0),
120
            bytes32(0),
121
            _col
122
        );
123

                            
                        
124
        _allTargets[1] = address(borrowerOperations);
125
        _allCalldatas[1] = abi.encodeWithSelector(BorrowerOperations.closeCdp.selector, _cdpId);
126

                            
                        
127
        _allTargets[2] = address(borrowerOperations);
128
        _allCalldatas[2] = abi.encodeWithSelector(
129
            BorrowerOperations.addColl.selector,
130
            _cdpId,
131
            _cdpId,
132
            _cdpId,
133
            _col
134
        );
135

                            
                        
136
        _allTargets[3] = address(borrowerOperations);
137
        _allCalldatas[3] = abi.encodeWithSelector(
138
            BorrowerOperations.withdrawColl.selector,
139
            _cdpId,
140
            _col,
141
            _cdpId,
142
            _cdpId
143
        );
144

                            
                        
145
        _allTargets[4] = address(borrowerOperations);
146
        _allCalldatas[4] = abi.encodeWithSelector(
147
            BorrowerOperations.withdrawDebt.selector,
148
            _cdpId,
149
            _EBTCAmount,
150
            _cdpId,
151
            _cdpId
152
        );
153

                            
                        
154
        _allTargets[5] = address(borrowerOperations);
155
        _allCalldatas[5] = abi.encodeWithSelector(
156
            BorrowerOperations.repayDebt.selector,
157
            _cdpId,
158
            _EBTCAmount,
159
            _cdpId,
160
            _cdpId
161
        );
162

                            
                        
163
        for (uint256 _j = 0; _j < _actions; ++_j) {
164
            _i = uint256(keccak256(abi.encodePacked(value, _j, _i))) % _allTargets.length;
165
            emit FlashLoanAction(_j, _i);
166

                            
                        
167
            _targets[_j] = _allTargets[_i];
168
            _calldatas[_j] = _allCalldatas[_i];
169
        }
170

                            
                        
171
        return abi.encode(_targets, _calldatas);
172
    }
173

                            
                        
174
    function _getFirstCdpWithIcrGteMcr() internal returns (bytes32) {
175
        bytes32 _cId = sortedCdps.getLast();
176
        address currentBorrower = sortedCdps.getOwnerAddress(_cId);
177
        // Find the first cdp with ICR >= MCR
178
        while (
179
            currentBorrower != address(0) &&
180
            cdpManager.getICR(_cId, priceFeedMock.getPrice()) < cdpManager.MCR()
181
        ) {
182
            _cId = sortedCdps.getPrev(_cId);
183
            currentBorrower = sortedCdps.getOwnerAddress(_cId);
184
        }
185
        return _cId;
186
    }
187

                            
                        
188
    function _atLeastOneCdpIsLiquidatable(
189
        Cdp[] memory cdps,
190
        bool isRecoveryModeBefore
191
    ) internal view returns (bool atLeastOneCdpIsLiquidatable) {
192
        for (uint256 i = 0; i < cdps.length; ++i) {
193
            if (
194
                cdps[i].icr < cdpManager.MCR() ||
195
                (cdps[i].icr < cdpManager.CCR() && isRecoveryModeBefore)
196
            ) {
197
                atLeastOneCdpIsLiquidatable = true;
198
                break;
199
            }
200
        }
201
    }
202

                            
                        
203
    ///////////////////////////////////////////////////////
204
    // CdpManager
205
    ///////////////////////////////////////////////////////
206

                            
                        
207
    function liquidate(uint _i) public setup {
208
        bool success;
209
        bytes memory returnData;
210

                            
                        
211
        require(cdpManager.getActiveCdpsCount() > 1, "Cannot liquidate last CDP");
212

                            
                        
213
        bytes32 _cdpId = _getRandomCdp(_i);
214

                            
                        
215
        (uint256 entireDebt, , ) = cdpManager.getDebtAndCollShares(_cdpId);
216
        require(entireDebt > 0, "CDP must have debt");
217

                            
                        
218
        _before(_cdpId);
219

                            
                        
220
        (success, returnData) = actor.proxy(
221
            address(cdpManager),
222
            abi.encodeWithSelector(CdpManager.liquidate.selector, _cdpId)
223
        );
224

                            
                        
225
        _after(_cdpId);
226

                            
                        
227
        if (success) {
228
            if (
229
                vars.newIcrBefore >= cdpManager.LICR() // 103% else liquidating locks in bad debt
230
            ) {
231
                // https://github.com/Badger-Finance/ebtc-fuzz-review/issues/5
232
                gte(vars.newTcrAfter, vars.newTcrBefore, L_12);
233
            }
234
            // https://github.com/Badger-Finance/ebtc-fuzz-review/issues/12
235
            t(
236
                vars.newIcrBefore < cdpManager.MCR() ||
237
                    (vars.newIcrBefore < cdpManager.CCR() && vars.isRecoveryModeBefore),
238
                L_01
239
            );
240
            if (
241
                vars.lastGracePeriodStartTimestampIsSetBefore &&
242
                vars.isRecoveryModeBefore &&
243
                vars.isRecoveryModeAfter
244
            ) {
245
                eq(
246
                    vars.lastGracePeriodStartTimestampBefore,
247
                    vars.lastGracePeriodStartTimestampAfter,
248
                    L_14
249
                );
250
            }
251

                            
                        
252
            if (!vars.isRecoveryModeBefore && vars.isRecoveryModeAfter) {
253
                t(
254
                    !vars.lastGracePeriodStartTimestampIsSetBefore &&
255
                        vars.lastGracePeriodStartTimestampIsSetAfter,
256
                    L_15
257
                );
258
            }
259

                            
                        
260
            if (vars.isRecoveryModeBefore && !vars.isRecoveryModeAfter) {
261
                t(!vars.lastGracePeriodStartTimestampIsSetAfter, L_16);
262
            }
263

                            
                        
264
            gte(
265
                vars.actorCollAfter,
266
                vars.actorCollBefore +
267
                    collateral.getPooledEthByShares(vars.liquidatorRewardSharesBefore),
268
                L_09
269
            );
270
        } else if (vars.sortedCdpsSizeBefore > _i) {
271
            assertRevertReasonNotEqual(returnData, "Panic(17)");
272
        }
273
    }
274

                            
                        
275
    function partialLiquidate(uint _i, uint _partialAmount) public setup {
276
        bool success;
277
        bytes memory returnData;
278

                            
                        
279
        require(cdpManager.getActiveCdpsCount() > 1, "Cannot liquidate last CDP");
280

                            
                        
281
        bytes32 _cdpId = _getRandomCdp(_i);
282

                            
                        
283
        (uint256 entireDebt, , ) = cdpManager.getDebtAndCollShares(_cdpId);
284
        require(entireDebt > 0, "CDP must have debt");
285

                            
                        
286
        _partialAmount = between(_partialAmount, 0, entireDebt);
287

                            
                        
288
        _before(_cdpId);
289

                            
                        
290
        (success, returnData) = actor.proxy(
291
            address(cdpManager),
292
            abi.encodeWithSelector(
293
                CdpManager.partiallyLiquidate.selector,
294
                _cdpId,
295
                _partialAmount,
296
                _cdpId,
297
                _cdpId
298
            )
299
        );
300

                            
                        
301
        _after(_cdpId);
302

                            
                        
303
        if (success) {
304
            lt(vars.cdpDebtAfter, vars.cdpDebtBefore, "Partial liquidation must reduce CDP debt");
305

                            
                        
306
            if (
307
                vars.newIcrBefore >= cdpManager.LICR() // 103% else liquidating locks in bad debt
308
            ) {
309
                // https://github.com/Badger-Finance/ebtc-fuzz-review/issues/5
310
                gte(vars.newTcrAfter, vars.newTcrBefore, L_12);
311
            }
312
            // https://github.com/Badger-Finance/ebtc-fuzz-review/issues/12
313
            t(
314
                vars.newIcrBefore < cdpManager.MCR() ||
315
                    (vars.newIcrBefore < cdpManager.CCR() && vars.isRecoveryModeBefore),
316
                L_01
317
            );
318

                            
                        
319
            eq(vars.sortedCdpsSizeAfter, vars.sortedCdpsSizeBefore, "L-17 : Partial Liquidations do not close Cdps");
320

                            
                        
321
            // https://github.com/Badger-Finance/ebtc-fuzz-review/issues/4
322
            if (vars.sortedCdpsSizeAfter == vars.sortedCdpsSizeBefore) {
323
                // CDP was not fully liquidated
324
                gte(
325
                    collateral.getPooledEthByShares(cdpManager.getCdpCollShares(_cdpId)),
326
                    borrowerOperations.MIN_NET_COLL(),
327
                    GENERAL_10
328
                );
329
            }
330

                            
                        
331
            if (
332
                vars.lastGracePeriodStartTimestampIsSetBefore &&
333
                vars.isRecoveryModeBefore &&
334
                vars.isRecoveryModeAfter
335
            ) {
336
                eq(
337
                    vars.lastGracePeriodStartTimestampBefore,
338
                    vars.lastGracePeriodStartTimestampAfter,
339
                    L_14
340
                );
341
            }
342

                            
                        
343
            if (!vars.isRecoveryModeBefore && vars.isRecoveryModeAfter) {
344
                t(
345
                    !vars.lastGracePeriodStartTimestampIsSetBefore &&
346
                        vars.lastGracePeriodStartTimestampIsSetAfter,
347
                    L_15
348
                );
349
            }
350

                            
                        
351
            if (vars.isRecoveryModeBefore && !vars.isRecoveryModeAfter) {
352
                t(!vars.lastGracePeriodStartTimestampIsSetAfter, L_16);
353
            }
354
        } else {
355
            assertRevertReasonNotEqual(returnData, "Panic(17)");
356
        }
357
    }
358

                            
                        
359
    function liquidateCdps(uint _n) public setup {
360
        bool success;
361
        bytes memory returnData;
362

                            
                        
363
        require(cdpManager.getActiveCdpsCount() > 1, "Cannot liquidate last CDP");
364

                            
                        
365
        _n = between(_n, 1, cdpManager.getActiveCdpsCount());
366

                            
                        
367
        Cdp[] memory cdpsBefore = _getCdpIdsAndICRs();
368

                            
                        
369
        _before(bytes32(0));
370

                            
                        
371
        bytes32[] memory batch = liquidationSequencer.sequenceLiqToBatchLiqWithPrice(
372
            _n,
373
            vars.priceBefore
374
        );
375

                            
                        
376
        (success, returnData) = actor.proxy(
377
            address(cdpManager),
378
            abi.encodeWithSelector(CdpManager.batchLiquidateCdps.selector, batch)
379
        );
380

                            
                        
381
        _after(bytes32(0));
382

                            
                        
383
        if (success) {
384
            Cdp[] memory cdpsAfter = _getCdpIdsAndICRs();
385

                            
                        
386
            Cdp[] memory cdpsLiquidated = _cdpIdsAndICRsDiff(cdpsBefore, cdpsAfter);
387
            gte(
388
                cdpsLiquidated.length,
389
                1,
390
                "liquidateCdps must liquidate at least 1 CDP when successful"
391
            );
392
            lte(cdpsLiquidated.length, _n, "liquidateCdps must not liquidate more than n CDPs");
393
            for (uint256 i = 0; i < cdpsLiquidated.length; ++i) {
394
                // https://github.com/Badger-Finance/ebtc-fuzz-review/issues/12
395
                t(
396
                    cdpsLiquidated[i].icr < cdpManager.MCR() ||
397
                        (cdpsLiquidated[i].icr < cdpManager.CCR() && vars.isRecoveryModeBefore),
398
                    L_01
399
                );
400
            }
401

                            
                        
402
            if (
403
                vars.lastGracePeriodStartTimestampIsSetBefore &&
404
                vars.isRecoveryModeBefore &&
405
                vars.isRecoveryModeAfter
406
            ) {
407
                eq(
408
                    vars.lastGracePeriodStartTimestampBefore,
409
                    vars.lastGracePeriodStartTimestampAfter,
410
                    L_14
411
                );
412
            }
413

                            
                        
414
            if (!vars.isRecoveryModeBefore && vars.isRecoveryModeAfter) {
415
                t(
416
                    !vars.lastGracePeriodStartTimestampIsSetBefore &&
417
                        vars.lastGracePeriodStartTimestampIsSetAfter,
418
                    L_15
419
                );
420
            }
421

                            
                        
422
            if (vars.isRecoveryModeBefore && !vars.isRecoveryModeAfter) {
423
                t(!vars.lastGracePeriodStartTimestampIsSetAfter, L_16);
424
            }
425
        } else if (vars.sortedCdpsSizeBefore > _n) {
426
            if (_atLeastOneCdpIsLiquidatable(cdpsBefore, vars.isRecoveryModeBefore)) {
427
                assertRevertReasonNotEqual(returnData, "Panic(17)");
428
            }
429
        }
430
    }
431

                            
                        
432
    function redeemCollateral(
433
        uint _EBTCAmount,
434
        uint _partialRedemptionHintNICR,
435
        uint _maxFeePercentage,
436
        uint _maxIterations
437
    ) public setup {
438
        bool success;
439
        bytes memory returnData;
440

                            
                        
441
        _EBTCAmount = between(_EBTCAmount, 0, eBTCToken.balanceOf(address(actor)));
442
        _maxIterations = between(_maxIterations, 0, 1);
443

                            
                        
444
        _maxFeePercentage = between(
445
            _maxFeePercentage,
446
            cdpManager.redemptionFeeFloor(),
447
            cdpManager.DECIMAL_PRECISION()
448
        );
449

                            
                        
450
        bytes32 _cdpId = _getFirstCdpWithIcrGteMcr();
451

                            
                        
452
        _before(_cdpId);
453

                            
                        
454
        (success, returnData) = actor.proxy(
455
            address(cdpManager),
456
            abi.encodeWithSelector(
457
                CdpManager.redeemCollateral.selector,
458
                _EBTCAmount,
459
                bytes32(0),
460
                bytes32(0),
461
                bytes32(0),
462
                _partialRedemptionHintNICR,
463
                _maxIterations,
464
                _maxFeePercentage
465
            )
466
        );
467

                            
                        
468
        require(success);
469

                            
                        
470
        _after(_cdpId);
471

                            
                        
472
        gt(vars.tcrBefore, cdpManager.MCR(), EBTC_02);
473
        if (_maxIterations == 1) {
474
            gte(vars.activePoolDebtBefore, vars.activePoolDebtAfter, CDPM_05);
475
            gte(vars.cdpDebtBefore, vars.cdpDebtAfter, CDPM_06);
476
            // TODO: CHECK THIS
477
            // https://github.com/Badger-Finance/ebtc-fuzz-review/issues/10#issuecomment-1702685732
478
            if (vars.sortedCdpsSizeBefore == vars.sortedCdpsSizeAfter) {
479
                // Redemptions do not reduce TCR
480
                // If redemptions do not close any CDP that was healthy (low debt, high coll)
481
                gt(
482
                    vars.newTcrAfter, // TODO: See how this breaks
483
                    vars.newTcrBefore,
484
                    R_07
485
                );
486
            }
487
            t(invariant_CDPM_04(vars), CDPM_04);
488
        }
489
        gt(vars.actorEbtcBefore, vars.actorEbtcAfter, R_08);
490

                            
                        
491
        // Verify Fee Recipient Received the Fee
492
        gte(vars.feeRecipientTotalCollAfter, vars.feeRecipientTotalCollBefore, F_02);
493

                            
                        
494
        if (
495
            vars.lastGracePeriodStartTimestampIsSetBefore &&
496
            vars.isRecoveryModeBefore &&
497
            vars.isRecoveryModeAfter
498
        ) {
499
            eq(
500
                vars.lastGracePeriodStartTimestampBefore,
501
                vars.lastGracePeriodStartTimestampAfter,
502
                L_14
503
            );
504
        }
505

                            
                        
506
        if (!vars.isRecoveryModeBefore && vars.isRecoveryModeAfter) {
507
            t(
508
                !vars.lastGracePeriodStartTimestampIsSetBefore &&
509
                    vars.lastGracePeriodStartTimestampIsSetAfter,
510
                L_15
511
            );
512
        }
513

                            
                        
514
        if (vars.isRecoveryModeBefore && !vars.isRecoveryModeAfter) {
515
            t(!vars.lastGracePeriodStartTimestampIsSetAfter, L_16);
516
        }
517
    }
518

                            
                        
519
    ///////////////////////////////////////////////////////
520
    // ActivePool
521
    ///////////////////////////////////////////////////////
522

                            
                        
523
    function flashLoanColl(uint _amount) internal setup {
524
        bool success;
525
        bytes memory returnData;
526

                            
                        
527
        _amount = between(_amount, 0, activePool.maxFlashLoan(address(collateral)));
528
        uint _fee = activePool.flashFee(address(collateral), _amount);
529

                            
                        
530
        _before(bytes32(0));
531

                            
                        
532
        // take the flashloan which should always cost the fee paid by caller
533
        uint _balBefore = collateral.balanceOf(activePool.feeRecipientAddress());
534
        (success, returnData) = actor.proxy(
535
            address(activePool),
536
            abi.encodeWithSelector(
537
                ActivePool.flashLoan.selector,
538
                IERC3156FlashBorrower(address(actor)),
539
                address(collateral),
540
                _amount,
541
                _getFlashLoanActions(_amount)
542
            )
543
        );
544

                            
                        
545
        require(success);
546

                            
                        
547
        _after(bytes32(0));
548

                            
                        
549
        uint _balAfter = collateral.balanceOf(activePool.feeRecipientAddress());
550
        eq(_balAfter - _balBefore, _fee, F_03);
551

                            
                        
552
        if (
553
            vars.lastGracePeriodStartTimestampIsSetBefore &&
554
            vars.isRecoveryModeBefore &&
555
            vars.isRecoveryModeAfter
556
        ) {
557
            eq(
558
                vars.lastGracePeriodStartTimestampBefore,
559
                vars.lastGracePeriodStartTimestampAfter,
560
                L_14
561
            );
562
        }
563

                            
                        
564
        if (!vars.isRecoveryModeBefore && vars.isRecoveryModeAfter) {
565
            t(
566
                !vars.lastGracePeriodStartTimestampIsSetBefore &&
567
                    vars.lastGracePeriodStartTimestampIsSetAfter,
568
                L_15
569
            );
570
        }
571

                            
                        
572
        if (vars.isRecoveryModeBefore && !vars.isRecoveryModeAfter) {
573
            t(!vars.lastGracePeriodStartTimestampIsSetAfter, L_16);
574
        }
575
    }
576

                            
                        
577
    ///////////////////////////////////////////////////////
578
    // BorrowerOperations
579
    ///////////////////////////////////////////////////////
580

                            
                        
581
    function flashLoanEBTC(uint _amount) internal setup {
582
        bool success;
583
        bytes memory returnData;
584

                            
                        
585
        _amount = between(_amount, 0, borrowerOperations.maxFlashLoan(address(eBTCToken)));
586

                            
                        
587
        uint _fee = borrowerOperations.flashFee(address(eBTCToken), _amount);
588

                            
                        
589
        _before(bytes32(0));
590

                            
                        
591
        // take the flashloan which should always cost the fee paid by caller
592
        uint _balBefore = eBTCToken.balanceOf(borrowerOperations.feeRecipientAddress());
593
        (success, returnData) = actor.proxy(
594
            address(borrowerOperations),
595
            abi.encodeWithSelector(
596
                BorrowerOperations.flashLoan.selector,
597
                IERC3156FlashBorrower(address(actor)),
598
                address(eBTCToken),
599
                _amount,
600
                _getFlashLoanActions(_amount)
601
            )
602
        );
603

                            
                        
604
        // BorrowerOperations.flashLoan may revert due to reentrancy
605
        require(success);
606

                            
                        
607
        _after(bytes32(0));
608

                            
                        
609
        uint _balAfter = eBTCToken.balanceOf(borrowerOperations.feeRecipientAddress());
610
        eq(_balAfter - _balBefore, _fee, F_03);
611

                            
                        
612
        if (
613
            vars.lastGracePeriodStartTimestampIsSetBefore &&
614
            vars.isRecoveryModeBefore &&
615
            vars.isRecoveryModeAfter
616
        ) {
617
            eq(
618
                vars.lastGracePeriodStartTimestampBefore,
619
                vars.lastGracePeriodStartTimestampAfter,
620
                L_14
621
            );
622
        }
623

                            
                        
624
        if (!vars.isRecoveryModeBefore && vars.isRecoveryModeAfter) {
625
            t(
626
                !vars.lastGracePeriodStartTimestampIsSetBefore &&
627
                    vars.lastGracePeriodStartTimestampIsSetAfter,
628
                L_15
629
            );
630
        }
631

                            
                        
632
        if (vars.isRecoveryModeBefore && !vars.isRecoveryModeAfter) {
633
            t(!vars.lastGracePeriodStartTimestampIsSetAfter, L_16);
634
        }
635
    }
636

                            
                        
637
    function openCdp(uint256 _col, uint256 _EBTCAmount) public setup returns (bytes32 _cdpId) {
638
        bool success;
639
        bytes memory returnData;
640

                            
                        
641
        // we pass in CCR instead of MCR in case it's the first one
642
        uint price = priceFeedMock.getPrice();
643

                            
                        
644
        uint256 requiredCollAmount = (_EBTCAmount * cdpManager.CCR()) / (price);
645
        uint256 minCollAmount = max(
646
            cdpManager.MIN_NET_COLL() + borrowerOperations.LIQUIDATOR_REWARD(),
647
            requiredCollAmount
648
        );
649
        uint256 maxCollAmount = min(2 * minCollAmount, INITIAL_COLL_BALANCE / 10);
650
        _col = between(requiredCollAmount, minCollAmount, maxCollAmount);
651

                            
                        
652
        (success, ) = actor.proxy(
653
            address(collateral),
654
            abi.encodeWithSelector(
655
                CollateralTokenTester.approve.selector,
656
                address(borrowerOperations),
657
                _col
658
            )
659
        );
660
        t(success, "Approve never fails");
661

                            
                        
662
        _before(bytes32(0));
663

                            
                        
664
        (success, returnData) = actor.proxy(
665
            address(borrowerOperations),
666
            abi.encodeWithSelector(
667
                BorrowerOperations.openCdp.selector,
668
                _EBTCAmount,
669
                bytes32(0),
670
                bytes32(0),
671
                _col
672
            )
673
        );
674

                            
                        
675
        if (success) {
676
            _cdpId = abi.decode(returnData, (bytes32));
677
            _after(_cdpId);
678

                            
                        
679
            t(invariant_GENERAL_01(vars), GENERAL_01);
680
            gt(vars.icrAfter, cdpManager.MCR(), BO_01);
681

                            
                        
682
            eq(vars.newTcrAfter, vars.tcrAfter, GENERAL_11);
683

                            
                        
684
            // https://github.com/Badger-Finance/ebtc-fuzz-review/issues/3
685
            t(invariant_GENERAL_09(cdpManager, vars), GENERAL_09);
686
            // https://github.com/Badger-Finance/ebtc-fuzz-review/issues/4
687
            gte(
688
                collateral.getPooledEthByShares(cdpManager.getCdpCollShares(_cdpId)),
689
                borrowerOperations.MIN_NET_COLL(),
690
                GENERAL_10
691
            );
692
            eq(
693
                vars.sortedCdpsSizeBefore + 1,
694
                vars.sortedCdpsSizeAfter,
695
                "CDPs count must have increased"
696
            );
697
            if (
698
                vars.lastGracePeriodStartTimestampIsSetBefore &&
699
                vars.isRecoveryModeBefore &&
700
                vars.isRecoveryModeAfter
701
            ) {
702
                eq(
703
                    vars.lastGracePeriodStartTimestampBefore,
704
                    vars.lastGracePeriodStartTimestampAfter,
705
                    L_14
706
                );
707
            }
708

                            
                        
709
            if (!vars.isRecoveryModeBefore && vars.isRecoveryModeAfter) {
710
                t(
711
                    !vars.lastGracePeriodStartTimestampIsSetBefore &&
712
                        vars.lastGracePeriodStartTimestampIsSetAfter,
713
                    L_15
714
                );
715
            }
716

                            
                        
717
            if (vars.isRecoveryModeBefore && !vars.isRecoveryModeAfter) {
718
                t(!vars.lastGracePeriodStartTimestampIsSetAfter, L_16);
719
            }
720
        } else {
721
            assertRevertReasonNotEqual(returnData, "Panic(17)");
722
        }
723
    }
724

                            
                        
725
    function addColl(uint _coll, uint256 _i) public setup {
726
        bool success;
727
        bytes memory returnData;
728

                            
                        
729
        uint256 numberOfCdps = sortedCdps.cdpCountOf(address(actor));
730
        require(numberOfCdps > 0, "Actor must have at least one CDP open");
731

                            
                        
732
        _i = between(_i, 0, numberOfCdps - 1);
733
        bytes32 _cdpId = sortedCdps.cdpOfOwnerByIndex(address(actor), _i);
734
        t(_cdpId != bytes32(0), "CDP ID must not be null if the index is valid");
735

                            
                        
736
        _coll = between(_coll, 0, INITIAL_COLL_BALANCE / 10);
737

                            
                        
738
        if (collateral.balanceOf(address(actor)) < _coll) {
739
            (success, ) = actor.proxy(
740
                address(collateral),
741
                abi.encodeWithSelector(CollateralTokenTester.deposit.selector, ""),
742
                (_coll - collateral.balanceOf(address(actor)))
743
            );
744
            require(success);
745
            require(
746
                collateral.balanceOf(address(actor)) > _coll,
747
                "Actor has high enough balance to add"
748
            );
749
        }
750

                            
                        
751
        (success, ) = actor.proxy(
752
            address(collateral),
753
            abi.encodeWithSelector(
754
                CollateralTokenTester.approve.selector,
755
                address(borrowerOperations),
756
                _coll
757
            )
758
        );
759
        t(success, "Approve never fails");
760

                            
                        
761
        _before(_cdpId);
762

                            
                        
763
        (success, returnData) = actor.proxy(
764
            address(borrowerOperations),
765
            abi.encodeWithSelector(
766
                BorrowerOperations.addColl.selector,
767
                _cdpId,
768
                _cdpId,
769
                _cdpId,
770
                _coll
771
            )
772
        );
773

                            
                        
774
        _after(_cdpId);
775

                            
                        
776
        if (success) {
777
            emit L3(
778
                vars.isRecoveryModeBefore ? 1 : 0,
779
                vars.hasGracePeriodPassedBefore ? 1 : 0,
780
                vars.icrAfter
781
            );
782
            emit L3(
783
                block.timestamp,
784
                cdpManager.lastGracePeriodStartTimestamp(),
785
                cdpManager.recoveryModeGracePeriodDuration()
786
            );
787

                            
                        
788
            eq(vars.newTcrAfter, vars.tcrAfter, GENERAL_11);
789
            gte(vars.nicrAfter, vars.nicrBefore, BO_03);
790
            // https://github.com/Badger-Finance/ebtc-fuzz-review/issues/3
791
            t(invariant_GENERAL_09(cdpManager, vars), GENERAL_09);
792
            // https://github.com/Badger-Finance/ebtc-fuzz-review/issues/4
793
            gte(
794
                collateral.getPooledEthByShares(cdpManager.getCdpCollShares(_cdpId)),
795
                borrowerOperations.MIN_NET_COLL(),
796
                GENERAL_10
797
            );
798

                            
                        
799
            t(invariant_GENERAL_01(vars), GENERAL_01);
800

                            
                        
801
            if (
802
                vars.lastGracePeriodStartTimestampIsSetBefore &&
803
                vars.isRecoveryModeBefore &&
804
                vars.isRecoveryModeAfter
805
            ) {
806
                eq(
807
                    vars.lastGracePeriodStartTimestampBefore,
808
                    vars.lastGracePeriodStartTimestampAfter,
809
                    L_14
810
                );
811
            }
812

                            
                        
813
            if (!vars.isRecoveryModeBefore && vars.isRecoveryModeAfter) {
814
                t(
815
                    !vars.lastGracePeriodStartTimestampIsSetBefore &&
816
                        vars.lastGracePeriodStartTimestampIsSetAfter,
817
                    L_15
818
                );
819
            }
820

                            
                        
821
            if (vars.isRecoveryModeBefore && !vars.isRecoveryModeAfter) {
822
                t(!vars.lastGracePeriodStartTimestampIsSetAfter, L_16);
823
            }
824
        } else {
825
            assertRevertReasonNotEqual(returnData, "Panic(17)");
826
        }
827
    }
828

                            
                        
829
    function withdrawColl(uint _amount, uint256 _i) public setup {
830
        bool success;
831
        bytes memory returnData;
832

                            
                        
833
        uint256 numberOfCdps = sortedCdps.cdpCountOf(address(actor));
834
        require(numberOfCdps > 0, "Actor must have at least one CDP open");
835

                            
                        
836
        _i = between(_i, 0, numberOfCdps - 1);
837
        bytes32 _cdpId = sortedCdps.cdpOfOwnerByIndex(address(actor), _i);
838
        t(_cdpId != bytes32(0), "CDP ID must not be null if the index is valid");
839

                            
                        
840
        // Can only withdraw up to CDP collateral amount, otherwise will revert with assert
841
        _amount = between(
842
            _amount,
843
            0,
844
            collateral.getPooledEthByShares(cdpManager.getCdpCollShares(_cdpId))
845
        );
846

                            
                        
847
        _before(_cdpId);
848

                            
                        
849
        (success, returnData) = actor.proxy(
850
            address(borrowerOperations),
851
            abi.encodeWithSelector(
852
                BorrowerOperations.withdrawColl.selector,
853
                _cdpId,
854
                _amount,
855
                _cdpId,
856
                _cdpId
857
            )
858
        );
859

                            
                        
860
        _after(_cdpId);
861

                            
                        
862
        if (success) {
863
            eq(vars.newTcrAfter, vars.tcrAfter, GENERAL_11);
864
            lte(vars.nicrAfter, vars.nicrBefore, BO_04);
865
            // https://github.com/Badger-Finance/ebtc-fuzz-review/issues/3
866
            t(invariant_GENERAL_09(cdpManager, vars), GENERAL_09);
867
            t(invariant_GENERAL_01(vars), GENERAL_01);
868
            // https://github.com/Badger-Finance/ebtc-fuzz-review/issues/4
869
            gte(
870
                collateral.getPooledEthByShares(cdpManager.getCdpCollShares(_cdpId)),
871
                borrowerOperations.MIN_NET_COLL(),
872
                GENERAL_10
873
            );
874

                            
                        
875
            if (
876
                vars.lastGracePeriodStartTimestampIsSetBefore &&
877
                vars.isRecoveryModeBefore &&
878
                vars.isRecoveryModeAfter
879
            ) {
880
                eq(
881
                    vars.lastGracePeriodStartTimestampBefore,
882
                    vars.lastGracePeriodStartTimestampAfter,
883
                    L_14
884
                );
885
            }
886

                            
                        
887
            if (!vars.isRecoveryModeBefore && vars.isRecoveryModeAfter) {
888
                t(
889
                    !vars.lastGracePeriodStartTimestampIsSetBefore &&
890
                        vars.lastGracePeriodStartTimestampIsSetAfter,
891
                    L_15
892
                );
893
            }
894

                            
                        
895
            if (vars.isRecoveryModeBefore && !vars.isRecoveryModeAfter) {
896
                t(!vars.lastGracePeriodStartTimestampIsSetAfter, L_16);
897
            }
898
        } else {
899
            assertRevertReasonNotEqual(returnData, "Panic(17)");
900
        }
901
    }
902

                            
                        
903
    function withdrawDebt(uint _amount, uint256 _i) public setup {
904
        bool success;
905
        bytes memory returnData;
906

                            
                        
907
        uint256 numberOfCdps = sortedCdps.cdpCountOf(address(actor));
908
        require(numberOfCdps > 0, "Actor must have at least one CDP open");
909

                            
                        
910
        _i = between(_i, 0, numberOfCdps - 1);
911
        bytes32 _cdpId = sortedCdps.cdpOfOwnerByIndex(address(actor), _i);
912
        t(_cdpId != bytes32(0), "CDP ID must not be null if the index is valid");
913

                            
                        
914
        // TODO verify the assumption below, maybe there's a more sensible (or Governance-defined/hardcoded) limit for the maximum amount of minted eBTC at a single operation
915
        // Can only withdraw up to type(uint128).max eBTC, so that `BorrwerOperations._getNewCdpAmounts` does not overflow
916
        _amount = between(_amount, 0, type(uint128).max);
917

                            
                        
918
        _before(_cdpId);
919

                            
                        
920
        (success, returnData) = actor.proxy(
921
            address(borrowerOperations),
922
            abi.encodeWithSelector(
923
                BorrowerOperations.withdrawDebt.selector,
924
                _cdpId,
925
                _amount,
926
                _cdpId,
927
                _cdpId
928
            )
929
        );
930

                            
                        
931
        require(success);
932

                            
                        
933
        _after(_cdpId);
934

                            
                        
935
        eq(vars.newTcrAfter, vars.tcrAfter, GENERAL_11);
936
        gte(vars.cdpDebtAfter, vars.cdpDebtBefore, "withdrawDebt must not decrease debt");
937
        eq(
938
            vars.actorEbtcAfter,
939
            vars.actorEbtcBefore + _amount,
940
            "withdrawDebt must increase debt by requested amount"
941
        );
942
        // https://github.com/Badger-Finance/ebtc-fuzz-review/issues/4
943
        gte(
944
            collateral.getPooledEthByShares(cdpManager.getCdpCollShares(_cdpId)),
945
            borrowerOperations.MIN_NET_COLL(),
946
            GENERAL_10
947
        );
948

                            
                        
949
        if (
950
            vars.lastGracePeriodStartTimestampIsSetBefore &&
951
            vars.isRecoveryModeBefore &&
952
            vars.isRecoveryModeAfter
953
        ) {
954
            eq(
955
                vars.lastGracePeriodStartTimestampBefore,
956
                vars.lastGracePeriodStartTimestampAfter,
957
                L_14
958
            );
959
        }
960

                            
                        
961
        if (!vars.isRecoveryModeBefore && vars.isRecoveryModeAfter) {
962
            t(
963
                !vars.lastGracePeriodStartTimestampIsSetBefore &&
964
                    vars.lastGracePeriodStartTimestampIsSetAfter,
965
                L_15
966
            );
967
        }
968

                            
                        
969
        if (vars.isRecoveryModeBefore && !vars.isRecoveryModeAfter) {
970
            t(!vars.lastGracePeriodStartTimestampIsSetAfter, L_16);
971
        }
972
    }
973

                            
                        
974
    function repayDebt(uint _amount, uint256 _i) public setup {
975
        bool success;
976
        bytes memory returnData;
977

                            
                        
978
        uint256 numberOfCdps = sortedCdps.cdpCountOf(address(actor));
979
        require(numberOfCdps > 0, "Actor must have at least one CDP open");
980

                            
                        
981
        _i = between(_i, 0, numberOfCdps - 1);
982
        bytes32 _cdpId = sortedCdps.cdpOfOwnerByIndex(address(actor), _i);
983
        t(_cdpId != bytes32(0), "CDP ID must not be null if the index is valid");
984

                            
                        
985
        (uint256 entireDebt, , ) = cdpManager.getDebtAndCollShares(_cdpId);
986
        _amount = between(_amount, 0, entireDebt);
987

                            
                        
988
        _before(_cdpId);
989

                            
                        
990
        (success, returnData) = actor.proxy(
991
            address(borrowerOperations),
992
            abi.encodeWithSelector(
993
                BorrowerOperations.repayDebt.selector,
994
                _cdpId,
995
                _amount,
996
                _cdpId,
997
                _cdpId
998
            )
999
        );
1000
        require(success);
1001

                            
                        
1002
        _after(_cdpId);
1003

                            
                        
1004
        eq(vars.newTcrAfter, vars.tcrAfter, GENERAL_11);
1005

                            
                        
1006
        // https://github.com/Badger-Finance/ebtc-fuzz-review/issues/3
1007
        gte(vars.newTcrAfter, vars.newTcrBefore, BO_08);
1008

                            
                        
1009
        eq(vars.ebtcTotalSupplyBefore - _amount, vars.ebtcTotalSupplyAfter, BO_07);
1010
        eq(vars.actorEbtcBefore - _amount, vars.actorEbtcAfter, BO_07);
1011
        // https://github.com/Badger-Finance/ebtc-fuzz-review/issues/3
1012
        t(invariant_GENERAL_09(cdpManager, vars), GENERAL_09);
1013
        t(invariant_GENERAL_01(vars), GENERAL_01);
1014
        // https://github.com/Badger-Finance/ebtc-fuzz-review/issues/4
1015
        gte(
1016
            collateral.getPooledEthByShares(cdpManager.getCdpCollShares(_cdpId)),
1017
            borrowerOperations.MIN_NET_COLL(),
1018
            GENERAL_10
1019
        );
1020

                            
                        
1021
        if (
1022
            vars.lastGracePeriodStartTimestampIsSetBefore &&
1023
            vars.isRecoveryModeBefore &&
1024
            vars.isRecoveryModeAfter
1025
        ) {
1026
            eq(
1027
                vars.lastGracePeriodStartTimestampBefore,
1028
                vars.lastGracePeriodStartTimestampAfter,
1029
                L_14
1030
            );
1031
        }
1032

                            
                        
1033
        if (!vars.isRecoveryModeBefore && vars.isRecoveryModeAfter) {
1034
            t(
1035
                !vars.lastGracePeriodStartTimestampIsSetBefore &&
1036
                    vars.lastGracePeriodStartTimestampIsSetAfter,
1037
                L_15
1038
            );
1039
        }
1040

                            
                        
1041
        if (vars.isRecoveryModeBefore && !vars.isRecoveryModeAfter) {
1042
            t(!vars.lastGracePeriodStartTimestampIsSetAfter, L_16);
1043
        }
1044
    }
1045

                            
                        
1046
    function closeCdp(uint _i) public setup {
1047
        bool success;
1048
        bytes memory returnData;
1049

                            
                        
1050
        require(cdpManager.getActiveCdpsCount() > 1, "Cannot close last CDP");
1051

                            
                        
1052
        uint256 numberOfCdps = sortedCdps.cdpCountOf(address(actor));
1053
        require(numberOfCdps > 0, "Actor must have at least one CDP open");
1054

                            
                        
1055
        _i = between(_i, 0, numberOfCdps - 1);
1056
        bytes32 _cdpId = sortedCdps.cdpOfOwnerByIndex(address(actor), _i);
1057
        t(_cdpId != bytes32(0), "CDP ID must not be null if the index is valid");
1058

                            
                        
1059
        _before(_cdpId);
1060

                            
                        
1061
        (success, returnData) = actor.proxy(
1062
            address(borrowerOperations),
1063
            abi.encodeWithSelector(BorrowerOperations.closeCdp.selector, _cdpId)
1064
        );
1065

                            
                        
1066
        _after(_cdpId);
1067

                            
                        
1068
        if (success) {
1069
            eq(vars.newTcrAfter, vars.tcrAfter, GENERAL_11);
1070
            eq(vars.cdpDebtAfter, 0, BO_02);
1071
            eq(
1072
                vars.sortedCdpsSizeBefore - 1,
1073
                vars.sortedCdpsSizeAfter,
1074
                "closeCdp reduces list size by 1"
1075
            );
1076
            gt(
1077
                vars.actorCollAfter,
1078
                vars.actorCollBefore,
1079
                "closeCdp increases the collateral balance of the user"
1080
            );
1081
            // https://github.com/Badger-Finance/ebtc-fuzz-review/issues/3
1082
            t(invariant_GENERAL_09(cdpManager, vars), GENERAL_09);
1083
            emit L4(
1084
                vars.actorCollBefore,
1085
                vars.cdpCollBefore,
1086
                vars.liquidatorRewardSharesBefore,
1087
                vars.actorCollAfter
1088
            );
1089
            gt(
1090
                // https://github.com/Badger-Finance/ebtc-fuzz-review/issues/11
1091
                // Note: not checking for strict equality since split fee is difficult to calculate a-priori, so the CDP collateral value may not be sent back to the user in full
1092
                vars.actorCollAfter,
1093
                vars.actorCollBefore +
1094
                    // ActivePool transfer SHARES not ETH directly
1095
                    collateral.getPooledEthByShares(vars.liquidatorRewardSharesBefore),
1096
                BO_05
1097
            );
1098
            t(invariant_GENERAL_01(vars), GENERAL_01);
1099

                            
                        
1100
            if (
1101
                vars.lastGracePeriodStartTimestampIsSetBefore &&
1102
                vars.isRecoveryModeBefore &&
1103
                vars.isRecoveryModeAfter
1104
            ) {
1105
                eq(
1106
                    vars.lastGracePeriodStartTimestampBefore,
1107
                    vars.lastGracePeriodStartTimestampAfter,
1108
                    L_14
1109
                );
1110
            }
1111

                            
                        
1112
            if (!vars.isRecoveryModeBefore && vars.isRecoveryModeAfter) {
1113
                t(
1114
                    !vars.lastGracePeriodStartTimestampIsSetBefore &&
1115
                        vars.lastGracePeriodStartTimestampIsSetAfter,
1116
                    L_15
1117
                );
1118
            }
1119

                            
                        
1120
            if (vars.isRecoveryModeBefore && !vars.isRecoveryModeAfter) {
1121
                t(!vars.lastGracePeriodStartTimestampIsSetAfter, L_16);
1122
            }
1123
        } else {
1124
            assertRevertReasonNotEqual(returnData, "Panic(17)");
1125
        }
1126
    }
1127

                            
                        
1128
    function adjustCdp(
1129
        uint _i,
1130
        uint _collWithdrawal,
1131
        uint _EBTCChange,
1132
        bool _isDebtIncrease
1133
    ) public setup {
1134
        bool success;
1135
        bytes memory returnData;
1136

                            
                        
1137
        uint256 numberOfCdps = sortedCdps.cdpCountOf(address(actor));
1138
        require(numberOfCdps > 0, "Actor must have at least one CDP open");
1139

                            
                        
1140
        _i = between(_i, 0, numberOfCdps - 1);
1141
        bytes32 _cdpId = sortedCdps.cdpOfOwnerByIndex(address(actor), _i);
1142
        t(_cdpId != bytes32(0), "CDP ID must not be null if the index is valid");
1143

                            
                        
1144
        (uint256 entireDebt, uint256 entireColl, ) = cdpManager.getDebtAndCollShares(_cdpId);
1145
        _collWithdrawal = between(_collWithdrawal, 0, entireColl);
1146
        _EBTCChange = between(_EBTCChange, 0, entireDebt);
1147

                            
                        
1148
        _before(_cdpId);
1149

                            
                        
1150
        (success, returnData) = actor.proxy(
1151
            address(borrowerOperations),
1152
            abi.encodeWithSelector(
1153
                BorrowerOperations.adjustCdp.selector,
1154
                _cdpId,
1155
                _collWithdrawal,
1156
                _EBTCChange,
1157
                _isDebtIncrease,
1158
                _cdpId,
1159
                _cdpId
1160
            )
1161
        );
1162

                            
                        
1163
        require(success);
1164

                            
                        
1165
        _after(_cdpId);
1166

                            
                        
1167
        eq(vars.newTcrAfter, vars.tcrAfter, GENERAL_11);
1168
        // https://github.com/Badger-Finance/ebtc-fuzz-review/issues/3
1169
        t(invariant_GENERAL_09(cdpManager, vars), GENERAL_09);
1170

                            
                        
1171
        t(invariant_GENERAL_01(vars), GENERAL_01);
1172
        // https://github.com/Badger-Finance/ebtc-fuzz-review/issues/4
1173
        gte(
1174
            collateral.getPooledEthByShares(cdpManager.getCdpCollShares(_cdpId)),
1175
            borrowerOperations.MIN_NET_COLL(),
1176
            GENERAL_10
1177
        );
1178

                            
                        
1179
        if (
1180
            vars.lastGracePeriodStartTimestampIsSetBefore &&
1181
            vars.isRecoveryModeBefore &&
1182
            vars.isRecoveryModeAfter
1183
        ) {
1184
            eq(
1185
                vars.lastGracePeriodStartTimestampBefore,
1186
                vars.lastGracePeriodStartTimestampAfter,
1187
                L_14
1188
            );
1189
        }
1190

                            
                        
1191
        if (!vars.isRecoveryModeBefore && vars.isRecoveryModeAfter) {
1192
            t(
1193
                !vars.lastGracePeriodStartTimestampIsSetBefore &&
1194
                    vars.lastGracePeriodStartTimestampIsSetAfter,
1195
                L_15
1196
            );
1197
        }
1198

                            
                        
1199
        if (vars.isRecoveryModeBefore && !vars.isRecoveryModeAfter) {
1200
            t(!vars.lastGracePeriodStartTimestampIsSetAfter, L_16);
1201
        }
1202
    }
1203

                            
                        
1204
    ///////////////////////////////////////////////////////
1205
    // Collateral Token (Test)
1206
    ///////////////////////////////////////////////////////
1207

                            
                        
1208
    // Example for real world slashing: https://twitter.com/LidoFinance/status/1646505631678107649
1209
    // > There are 11 slashing ongoing with the RockLogic GmbH node operator in Lido.
1210
    // > the total projected impact is around 20 ETH,
1211
    // > or about 3% of average daily protocol rewards/0.0004% of TVL.
1212
    function setEthPerShare(uint256 _newEthPerShare) public {
1213
        uint256 currentEthPerShare = collateral.getEthPerShare();
1214
        _newEthPerShare = between(
1215
            _newEthPerShare,
1216
            (currentEthPerShare * 1e18) / MAX_REBASE_PERCENT,
1217
            (currentEthPerShare * MAX_REBASE_PERCENT) / 1e18
1218
        );
1219
        collateral.setEthPerShare(_newEthPerShare);
1220
    }
1221

                            
                        
1222
    ///////////////////////////////////////////////////////
1223
    // PriceFeed
1224
    ///////////////////////////////////////////////////////
1225

                            
                        
1226
    function setPrice(uint256 _newPrice) public {
1227
        uint256 currentPrice = priceFeedMock.getPrice();
1228
        _newPrice = between(
1229
            _newPrice,
1230
            (currentPrice * 1e18) / MAX_PRICE_CHANGE_PERCENT,
1231
            (currentPrice * MAX_PRICE_CHANGE_PERCENT) / 1e18
1232
        );
1233
        priceFeedMock.setPrice(_newPrice);
1234
    }
1235

                            
                        
1236
    ///////////////////////////////////////////////////////
1237
    // Governance
1238
    ///////////////////////////////////////////////////////
1239

                            
                        
1240
    function setGovernanceParameters(uint256 parameter, uint256 value) public {
1241
        parameter = between(parameter, 0, 6);
1242

                            
                        
1243
        if (parameter == 0) {
1244
            value = between(value, cdpManager.MINIMUM_GRACE_PERIOD(), type(uint128).max);
1245
            hevm.prank(defaultGovernance);
1246
            cdpManager.setGracePeriod(uint128(value));
1247
        } else if (parameter == 1) {
1248
            value = between(value, 0, activePool.getFeeRecipientClaimableCollShares());
1249
            _before(bytes32(0));
1250
            hevm.prank(defaultGovernance);
1251
            activePool.claimFeeRecipientCollShares(value);
1252
            _after(bytes32(0));
1253
            // If there was something to claim
1254
            if(value > 0) {
1255
                // https://github.com/Badger-Finance/ebtc-fuzz-review/issues/22
1256
                // Claiming will increase the balance
1257
                gte(vars.feeRecipientCollSharesAfter, vars.feeRecipientCollSharesBefore, F_01);
1258
            }
1259
        } else if (parameter == 2) {
1260
            value = between(value, 0, cdpManager.MAX_REWARD_SPLIT());
1261
            hevm.prank(defaultGovernance);
1262
            cdpManager.setStakingRewardSplit(value);
1263
        } else if (parameter == 3) {
1264
            value = between(
1265
                value,
1266
                cdpManager.MIN_REDEMPTION_FEE_FLOOR(),
1267
                cdpManager.DECIMAL_PRECISION()
1268
            );
1269
            hevm.prank(defaultGovernance);
1270
            cdpManager.setRedemptionFeeFloor(value);
1271
        } else if (parameter == 4) {
1272
            value = between(
1273
                value,
1274
                cdpManager.MIN_MINUTE_DECAY_FACTOR(),
1275
                cdpManager.MAX_MINUTE_DECAY_FACTOR()
1276
            );
1277
            hevm.prank(defaultGovernance);
1278
            cdpManager.setMinuteDecayFactor(value);
1279
        } else if (parameter == 5) {
1280
            value = between(value, 0, cdpManager.DECIMAL_PRECISION());
1281
            hevm.prank(defaultGovernance);
1282
            cdpManager.setBeta(value);
1283
        } else if (parameter == 6) {
1284
            value = between(value, 0, 1);
1285
            hevm.prank(defaultGovernance);
1286
            cdpManager.setRedemptionsPaused(value == 1 ? true : false);
1287
        }
1288
    }
1289
}
1290

                            
                        

Lines covered: 8 / 8 (100.0%)

1
pragma solidity 0.8.17;
2

                            
                        
3
import "@crytic/properties/contracts/util/PropertiesHelper.sol";
4
import "../Asserts.sol";
5

                            
                        
6
abstract contract EchidnaAsserts is PropertiesAsserts, Asserts {
7
    function gt(uint256 a, uint256 b, string memory message) internal override {
8
        assertGt(a, b, message);
9
    }
10

                            
                        
11
    function lt(uint256 a, uint256 b, string memory message) internal override {
12
        assertLt(a, b, message);
13
    }
14

                            
                        
15
    function gte(uint256 a, uint256 b, string memory message) internal override {
16
        assertGte(a, b, message);
17
    }
18

                            
                        
19
    function lte(uint256 a, uint256 b, string memory message) internal override {
20
        assertLte(a, b, message);
21
    }
22

                            
                        
23
    function eq(uint256 a, uint256 b, string memory message) internal override {
24
        assertEq(a, b, message);
25
    }
26

                            
                        
27
    function t(bool a, string memory message) internal override {
28
        assertWithMsg(a, message);
29
    }
30

                            
                        
31
    function between(uint256 value, uint256 low, uint256 high) internal override returns (uint256) {
32
        return clampBetween(value, low, high);
33
    }
34
}
35

                            
                        

Lines covered: 56 / 56 (100.0%)

1
pragma solidity 0.8.17;
2

                            
                        
3
import {TargetContractSetup} from "../TargetContractSetup.sol";
4
import {Properties} from "../Properties.sol";
5

                            
                        
6
abstract contract EchidnaProperties is TargetContractSetup, Properties {
7
    function echidna_price() public returns (bool) {
8
        return invariant_DUMMY_01(priceFeedMock);
9
    }
10

                            
                        
11
    function echidna_active_pool_invariant_1() public returns (bool) {
12
        return invariant_AP_01(collateral, activePool);
13
    }
14

                            
                        
15
    function echidna_active_pool_invariant_2() public returns (bool) {
16
        return invariant_AP_02(cdpManager, activePool);
17
    }
18

                            
                        
19
    function echidna_active_pool_invariant_3() public returns (bool) {
20
        return invariant_AP_03(eBTCToken, activePool);
21
    }
22

                            
                        
23
    function echidna_active_pool_invariant_4() public returns (bool) {
24
        return invariant_AP_04(cdpManager, activePool, diff_tolerance);
25
    }
26

                            
                        
27
    function echidna_active_pool_invariant_5() public returns (bool) {
28
        return invariant_AP_05(cdpManager, diff_tolerance);
29
    }
30

                            
                        
31
    function echidna_cdp_manager_invariant_1() public returns (bool) {
32
        return invariant_CDPM_01(cdpManager, sortedCdps);
33
    }
34

                            
                        
35
    function echidna_cdp_manager_invariant_2() public returns (bool) {
36
        return invariant_CDPM_02(cdpManager);
37
    }
38

                            
                        
39
    function echidna_cdp_manager_invariant_3() public returns (bool) {
40
        return invariant_CDPM_03(cdpManager);
41
    }
42

                            
                        
43
    // CDPM_04 is a vars invariant
44

                            
                        
45
    function echidna_coll_surplus_pool_invariant_1() public returns (bool) {
46
        return invariant_CSP_01(collateral, collSurplusPool);
47
    }
48

                            
                        
49
    function echidna_coll_surplus_pool_invariant_2() public returns (bool) {
50
        return invariant_CSP_02(collSurplusPool);
51
    }
52

                            
                        
53
    function echidna_sorted_list_invariant_1() public returns (bool) {
54
        return invariant_SL_01(cdpManager, sortedCdps);
55
    }
56

                            
                        
57
    function echidna_sorted_list_invariant_2() public returns (bool) {
58
        return invariant_SL_02(cdpManager, sortedCdps, priceFeedMock);
59
    }
60

                            
                        
61
    function echidna_sorted_list_invariant_3() public returns (bool) {
62
        return invariant_SL_03(cdpManager, priceFeedMock, sortedCdps);
63
    }
64

                            
                        
65
    // https://github.com/Badger-Finance/ebtc-fuzz-review/issues/15
66
    function echidna_sorted_list_invariant_5() public returns (bool) {
67
        return invariant_SL_05(crLens, sortedCdps);
68
    }
69

                            
                        
70
    // invariant_GENERAL_01 is a vars invariant
71

                            
                        
72
    function echidna_GENERAL_02() public returns (bool) {
73
        return invariant_GENERAL_02(cdpManager, priceFeedMock, eBTCToken);
74
    }
75

                            
                        
76
    function echidna_GENERAL_03() public returns (bool) {
77
        return invariant_GENERAL_03(cdpManager, borrowerOperations, eBTCToken, collateral);
78
    }
79

                            
                        
80
    function echidna_GENERAL_05() public returns (bool) {
81
        return invariant_GENERAL_05(activePool, cdpManager, collateral);
82
    }
83

                            
                        
84
    function echidna_GENERAL_05_B() public returns (bool) {
85
        return invariant_GENERAL_05_B(collSurplusPool, collateral);
86
    }
87

                            
                        
88
    function echidna_GENERAL_06() public returns (bool) {
89
        return invariant_GENERAL_06(eBTCToken, cdpManager, sortedCdps);
90
    }
91

                            
                        
92
    function echidna_GENERAL_08() public returns (bool) {
93
        return invariant_GENERAL_08(cdpManager, sortedCdps, priceFeedMock, collateral);
94
    }
95

                            
                        
96
    // invariant_GENERAL_09 is a vars
97

                            
                        
98
    function echidna_GENERAL_12() public returns (bool) {
99
        return invariant_GENERAL_12(cdpManager, priceFeedMock, crLens);
100
    }
101

                            
                        
102
    function echidna_GENERAL_13() public returns (bool) {
103
        return invariant_GENERAL_13(crLens, cdpManager, priceFeedMock, sortedCdps);
104
    }
105

                            
                        
106
    function echidna_GENERAL_14() public returns (bool) {
107
        return invariant_GENERAL_14(crLens, cdpManager, sortedCdps);
108
    }
109

                            
                        
110
    function echidna_GENERAL_15() public returns (bool) {
111
        return invariant_GENERAL_15();
112
    }
113

                            
                        
114
    function echidna_LS_01() public returns (bool) {
115
        return
116
            invariant_LS_01(
117
                cdpManager,
118
                liquidationSequencer,
119
                syncedLiquidationSequencer,
120
                priceFeedMock
121
            );
122
    }
123

                            
                        
124
}
125

                            
                        

Lines covered: 3 / 3 (100.0%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
import "./EchidnaAsserts.sol";
6
import "./EchidnaProperties.sol";
7
import "../TargetFunctions.sol";
8

                            
                        
9
contract EchidnaTester is EchidnaAsserts, EchidnaProperties, TargetFunctions {
10
    constructor() payable {
11
        _setUp();
12
        _setUpActors();
13
    }
14
}
15

                            
                        

Lines covered: 11 / 35 (31.4%)

1
// SPDX-License-Identifier: MIT
2

                            
                        
3
pragma solidity 0.8.17;
4

                            
                        
5
import "../../Interfaces/IPriceFeed.sol";
6
import "../../Interfaces/IFallbackCaller.sol";
7
import "../../Dependencies/Ownable.sol";
8
import "../../Dependencies/AuthNoOwner.sol";
9

                            
                        
10
/*
11
 * PriceFeed placeholder for testnet and development. The price can be manually input or fetched from
12
   the Fallback's TestNet implementation. Backwards compatible with local test environment as it defaults to use
13
   the manual price.
14
 */
15
contract PriceFeedTestnet is IPriceFeed, Ownable, AuthNoOwner {
16
    // --- variables ---
17

                            
                        
18
    uint256 private _price = 7428 * 1e13; // stETH/BTC price == ~15.8118 ETH per BTC
19
    bool public _useFallback;
20
    IFallbackCaller public fallbackCaller; // Wrapper contract that calls the Fallback system
21

                            
                        
22
    constructor(address _authorityAddress) {
23
        _initializeAuthority(_authorityAddress);
24
    }
25

                            
                        
26
    // --- Dependency setters ---
27

                            
                        
28
    function setAddresses(
29
        address _priceAggregatorAddress, // Not used but kept for compatibility with deployment script
30
        address _fallbackCallerAddress,
31
        address _authorityAddress
32
    ) external onlyOwner {
33
        fallbackCaller = IFallbackCaller(_fallbackCallerAddress);
34

                            
                        
35
        _initializeAuthority(_authorityAddress);
36

                            
                        
37
        renounceOwnership();
38
    }
39

                            
                        
40
    // --- Functions ---
41

                            
                        
42
    // View price getter for simplicity in tests
43
    function getPrice() external view returns (uint256) {
44
        return _price;
45
    }
46

                            
                        
47
    function fetchPrice() external override returns (uint256) {
48
        // Fire an event just like the mainnet version would.
49
        // This lets the subgraph rely on events to get the latest price even when developing locally.
50
        if (_useFallback) {
51
            FallbackResponse memory fallbackResponse = _getCurrentFallbackResponse();
52
            if (fallbackResponse.success) {
53
                _price = fallbackResponse.answer;
54
            }
55
        }
56
        emit LastGoodPriceUpdated(_price);
57
        return _price;
58
    }
59

                            
                        
60
    // Manual external price setter.
61
    function setPrice(uint256 price) external returns (bool) {
62
        _price = price;
63
        return true;
64
    }
65

                            
                        
66
    // Manual toggle use of Tellor testnet feed
67
    function toggleUseFallback() external returns (bool) {
68
        _useFallback = !_useFallback;
69
        return _useFallback;
70
    }
71

                            
                        
72
    function setFallbackCaller(address _fallbackCaller) external requiresAuth {
73
        address oldFallbackCaller = address(fallbackCaller);
74
        fallbackCaller = IFallbackCaller(_fallbackCaller);
75
        emit FallbackCallerChanged(oldFallbackCaller, _fallbackCaller);
76
    }
77

                            
                        
78
    // --- Oracle response wrapper functions ---
79
    /*
80
     * "_getCurrentFallbackResponse" fetches stETH/BTC from the Fallback, and returns it as a
81
     * FallbackResponse struct.
82
     */
83
    function _getCurrentFallbackResponse()
84
        internal
85
        view
86
        returns (FallbackResponse memory fallbackResponse)
87
    {
88
        uint256 stEthBtcValue;
89
        uint256 stEthBtcTimestamp;
90
        bool stEthBtcRetrieved;
91

                            
                        
92
        // Attempt to get the Fallback's stETH/BTC price
93
        try fallbackCaller.getFallbackResponse() returns (
94
            uint256 answer,
95
            uint256 timestampRetrieved,
96
            bool success
97
        ) {
98
            fallbackResponse.answer = answer;
99
            fallbackResponse.timestamp = timestampRetrieved;
100
            fallbackResponse.success = success;
101
        } catch {
102
            return (fallbackResponse);
103
        }
104
        return (fallbackResponse);
105
    }
106
}
107

                            
                        

Lines covered: 1 / 1 (100.0%)

1
// SPDX-License-Identifier: Unlicense
2
pragma solidity ^0.8.0;
3

                            
                        
4
interface IHevm {
5
    // Set block.timestamp to newTimestamp
6
    function warp(uint256 newTimestamp) external;
7

                            
                        
8
    // Set block.number to newNumber
9
    function roll(uint256 newNumber) external;
10

                            
                        
11
    // Loads a storage slot from an address
12
    function load(address where, bytes32 slot) external returns (bytes32);
13

                            
                        
14
    // Stores a value to an address' storage slot
15
    function store(address where, bytes32 slot, bytes32 value) external;
16

                            
                        
17
    // Signs data (privateKey, digest) => (r, v, s)
18
    function sign(uint256 privateKey, bytes32 digest) external returns (uint8 r, bytes32 v, bytes32 s);
19

                            
                        
20
    // Gets address for a given private key
21
    function addr(uint256 privateKey) external returns (address addr);
22

                            
                        
23
    // Performs a foreign function call via terminal
24
    function ffi(string[] calldata inputs) external returns (bytes memory result);
25
    
26
    // Performs the next smart contract call with specified `msg.sender`
27
    function prank(address newSender) external;
28
}
29

                            
                        
30
IHevm constant hevm = IHevm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);

Lines covered: 3 / 3 (100.0%)

1
pragma solidity ^0.8.0;
2

                            
                        
3
abstract contract PropertiesConstants {
4
    // Constant echidna addresses
5
    address constant USER1 = address(0x10000);
6
    address constant USER2 = address(0x20000);
7
    address constant USER3 = address(0x30000);
8
    uint256 constant INITIAL_BALANCE = 1000e18;
9
}
10

                            
                        

Lines covered: 36 / 57 (63.2%)

1
pragma solidity ^0.8.0;
2

                            
                        
3
abstract contract PropertiesAsserts {
4
    event LogUint256(string,uint256);
5
    event LogAddress(string, address);
6
    event LogString(string);
7

                            
                        
8
    event AssertFail(string);
9
    event AssertEqFail(string);
10
    event AssertNeqFail(string);
11
    event AssertGteFail(string);
12
    event AssertGtFail(string);
13
    event AssertLteFail(string);
14
    event AssertLtFail(string);
15

                            
                        
16
    function assertWithMsg(bool b, string memory reason) internal {
17
        if(!b){
18
            emit AssertFail(reason);
19
            assert(false);
20
        }
21
    }
22

                            
                        
23
    /// @notice asserts that a is equal to b. Violations are logged using reason.
24
    function assertEq(uint256 a, uint256 b, string memory reason) internal {
25
        if(a != b){
26
            string memory aStr = PropertiesLibString.toString(a);
27
            string memory bStr = PropertiesLibString.toString(b);
28
            bytes memory assertMsg = abi.encodePacked("Invalid: ", aStr,"!=",bStr,", reason: ", reason);
29
            emit AssertEqFail(string(assertMsg));
30
            assert(false);
31
        }
32
    }
33

                            
                        
34
    /// @notice int256 version of assertEq
35
    function assertEq(int256 a, int256 b, string memory reason) internal {
36
        if(a != b){
37
            string memory aStr = PropertiesLibString.toString(a);
38
            string memory bStr = PropertiesLibString.toString(b);
39
            bytes memory assertMsg = abi.encodePacked("Invalid: ", aStr,"!=",bStr,", reason: ", reason);
40
            emit AssertEqFail(string(assertMsg));
41
            assert(false);
42
        }
43
    }
44

                            
                        
45
    /// @notice asserts that a is not equal to b. Violations are logged using reason.
46
    function assertNeq(uint256 a, uint256 b, string memory reason) internal {
47
        if(a == b){
48
            string memory aStr = PropertiesLibString.toString(a);
49
            string memory bStr = PropertiesLibString.toString(b);
50
            bytes memory assertMsg = abi.encodePacked("Invalid: ", aStr,"==",bStr,", reason: ", reason);
51
            emit AssertNeqFail(string(assertMsg));
52
            assert(false);
53
        }
54
    }
55

                            
                        
56
    /// @notice int256 version of assertNeq
57
    function assertNeq(int256 a, int256 b, string memory reason) internal {
58
        if(a == b){
59
            string memory aStr = PropertiesLibString.toString(a);
60
            string memory bStr = PropertiesLibString.toString(b);
61
            bytes memory assertMsg = abi.encodePacked("Invalid: ", aStr,"==",bStr,", reason: ", reason);
62
            emit AssertNeqFail(string(assertMsg));
63
            assert(false);
64
        }
65
    }
66

                            
                        
67
    /// @notice asserts that a is greater than or equal to b. Violations are logged using reason.
68
    function assertGte(uint256 a, uint256 b, string memory reason) internal {
69
        if(!(a >= b)) {
70
            string memory aStr = PropertiesLibString.toString(a);
71
            string memory bStr = PropertiesLibString.toString(b);
72
            bytes memory assertMsg = abi.encodePacked("Invalid: ", aStr,"<",bStr," failed, reason: ", reason);
73
            emit AssertGteFail(string(assertMsg));
74
            assert(false);
75
        }
76
    }
77

                            
                        
78
    /// @notice int256 version of assertGte
79
    function assertGte(int256 a, int256 b, string memory reason) internal {
80
        if(!(a >= b)) {
81
            string memory aStr = PropertiesLibString.toString(a);
82
            string memory bStr = PropertiesLibString.toString(b);
83
            bytes memory assertMsg = abi.encodePacked("Invalid: ", aStr,"<",bStr," failed, reason: ", reason);
84
            emit AssertGteFail(string(assertMsg));
85
            assert(false);
86
        }
87
    }
88

                            
                        
89
    /// @notice asserts that a is greater than b. Violations are logged using reason.
90
    function assertGt(uint256 a, uint256 b, string memory reason) internal {
91
        if(!(a > b)) {
92
            string memory aStr = PropertiesLibString.toString(a);
93
            string memory bStr = PropertiesLibString.toString(b);
94
            bytes memory assertMsg = abi.encodePacked("Invalid: ", aStr,"<=", bStr," failed, reason: ", reason);
95
            emit AssertGtFail(string(assertMsg));
96
            assert(false);
97
        }
98
    }
99

                            
                        
100
    /// @notice int256 version of assertGt
101
    function assertGt(int256 a, int256 b, string memory reason) internal {
102
        if(!(a > b)) {
103
            string memory aStr = PropertiesLibString.toString(a);
104
            string memory bStr = PropertiesLibString.toString(b);
105
            bytes memory assertMsg = abi.encodePacked("Invalid: ", aStr,"<=", bStr," failed, reason: ", reason);
106
            emit AssertGtFail(string(assertMsg));
107
            assert(false);
108
        }
109
    }
110

                            
                        
111
    /// @notice asserts that a is less than or equal to b. Violations are logged using reason.
112
    function assertLte(uint256 a, uint256 b, string memory reason) internal {
113
        if(!(a <= b)) {
114
            string memory aStr = PropertiesLibString.toString(a);
115
            string memory bStr = PropertiesLibString.toString(b);
116
            bytes memory assertMsg = abi.encodePacked("Invalid: ", aStr,">", bStr," failed, reason: ", reason);
117
            emit AssertLteFail(string(assertMsg));
118
            assert(false);
119
        }
120
    }
121

                            
                        
122
    /// @notice int256 version of assertLte
123
    function assertLte(int256 a, int256 b, string memory reason) internal {
124
        if(!(a <= b)) {
125
            string memory aStr = PropertiesLibString.toString(a);
126
            string memory bStr = PropertiesLibString.toString(b);
127
            bytes memory assertMsg = abi.encodePacked("Invalid: ", aStr,">", bStr," failed, reason: ", reason);
128
            emit AssertLteFail(string(assertMsg));
129
            assert(false);
130
        }
131
    }
132

                            
                        
133
    /// @notice asserts that a is less than b. Violations are logged using reason.
134
    function assertLt(uint256 a, uint256 b, string memory reason) internal {
135
        if(!(a < b)) {
136
            string memory aStr = PropertiesLibString.toString(a);
137
            string memory bStr = PropertiesLibString.toString(b);
138
            bytes memory assertMsg = abi.encodePacked("Invalid: ", aStr,">=",bStr," failed, reason: ", reason);
139
            emit AssertLtFail(string(assertMsg));
140
            assert(false);
141
        }
142
    }
143

                            
                        
144
    /// @notice int256 version of assertLt
145
    function assertLt(int256 a, int256 b, string memory reason) internal {
146
        if(!(a < b)) {
147
            string memory aStr = PropertiesLibString.toString(a);
148
            string memory bStr = PropertiesLibString.toString(b);
149
            bytes memory assertMsg = abi.encodePacked("Invalid: ", aStr,">=",bStr," failed, reason: ", reason);
150
            emit AssertLtFail(string(assertMsg));
151
            assert(false);
152
        }
153
    }
154

                            
                        
155
    /// @notice Clamps value to be between low and high, both inclusive
156
    function clampBetween(uint256 value, uint256 low, uint256 high) internal returns (uint256) {
157
        if(value < low || value > high) {
158
            uint ans = low + (value % (high - low + 1));
159
            string memory valueStr = PropertiesLibString.toString(value);
160
            string memory ansStr = PropertiesLibString.toString(ans);
161
            bytes memory message = abi.encodePacked("Clamping value ", valueStr, " to ", ansStr);
162
            emit LogString(string(message));
163
            return ans;
164
        }
165
        return value;
166
    }
167

                            
                        
168
    /// @notice int256 version of clampBetween
169
    function clampBetween(int256 value, int256 low, int256 high) internal returns (int256) {
170
        if(value < low || value > high) {
171
            int range = high - low + 1;
172
            int clamped = (value - low) % (range);
173
            if (clamped < 0) clamped += range;
174
            int ans = low + clamped;
175
            string memory valueStr = PropertiesLibString.toString(value);
176
            string memory ansStr = PropertiesLibString.toString(ans);
177
            bytes memory message = abi.encodePacked("Clamping value ", valueStr, " to ", ansStr);
178
            emit LogString(string(message));
179
            return ans;
180
        }
181
        return value;
182
    }
183

                            
                        
184
    /// @notice clamps a to be less than b
185
    function clampLt(uint256 a, uint256 b) internal returns (uint256){
186
        if ( !(a < b)) {
187
            assertNeq(b, 0, "clampLt cannot clamp value a to be less than zero. Check your inputs/assumptions.");
188
            uint256 value = a % b;
189
            string memory aStr = PropertiesLibString.toString(a);
190
            string memory valueStr = PropertiesLibString.toString(value);
191
            bytes memory message = abi.encodePacked("Clamping value ", aStr, " to ", valueStr);
192
            emit LogString(string(message));
193
            return value;
194
        }
195
        return a;
196
    }
197

                            
                        
198
    /// @notice int256 version of clampLt
199
    function clampLt(int256 a, int256 b) internal returns (int256){
200
        if ( !(a < b)) {
201
            int256 value = b-1;
202
            string memory aStr = PropertiesLibString.toString(a);
203
            string memory valueStr = PropertiesLibString.toString(value);
204
            bytes memory message = abi.encodePacked("Clamping value ", aStr, " to ", valueStr);
205
            emit LogString(string(message));
206
            return value;
207
        }
208
        return a;
209
    }
210

                            
                        
211
    /// @notice clamps a to be less than or equal to b
212
    function clampLte(uint256 a, uint256 b) internal returns (uint256) {
213
        if(!(a <= b)) {
214
            uint256 value = a % (b+1);
215
            string memory aStr = PropertiesLibString.toString(a);
216
            string memory valueStr = PropertiesLibString.toString(value);
217
            bytes memory message = abi.encodePacked("Clamping value ", aStr, " to ", valueStr);
218
            emit LogString(string(message));
219
            return value;
220
        }
221
        return a;
222
    }
223

                            
                        
224
    /// @notice int256 version of clampLte
225
    function clampLte(int256 a, int256 b) internal returns (int256) {
226
        if(!(a <= b)) {
227
            int256 value = b;
228
            string memory aStr = PropertiesLibString.toString(a);
229
            string memory valueStr = PropertiesLibString.toString(value);
230
            bytes memory message = abi.encodePacked("Clamping value ", aStr, " to ", valueStr);
231
            emit LogString(string(message));
232
            return value;
233
        }
234
        return a;
235
    }
236

                            
                        
237
    /// @notice clamps a to be greater than b
238
    function clampGt(uint256 a, uint256 b) internal returns (uint256) {
239
        if(!(a > b)){
240
            assertNeq(b, type(uint256).max, "clampGt cannot clamp value a to be larger than uint256.max. Check your inputs/assumptions.");
241
            uint256 value = b+1;
242
            string memory aStr = PropertiesLibString.toString(a);
243
            string memory valueStr = PropertiesLibString.toString(value);
244
            bytes memory message = abi.encodePacked("Clamping value ", aStr, " to ", valueStr);
245
            emit LogString(string(message));
246
            return value;
247
        } else {
248
            return a;
249
        }
250
    }
251

                            
                        
252
    /// @notice int256 version of clampGt
253
    function clampGt(int256 a, int256 b) internal returns (int256) {
254
        if(!(a > b)){
255
            int256 value = b+1;
256
            string memory aStr = PropertiesLibString.toString(a);
257
            string memory valueStr = PropertiesLibString.toString(value);
258
            bytes memory message = abi.encodePacked("Clamping value ", aStr, " to ", valueStr);
259
            emit LogString(string(message));
260
            return value;
261
        } else {
262
            return a;
263
        }
264
    }
265

                            
                        
266
    /// @notice clamps a to be greater than or equal to b
267
    function clampGte(uint256 a, uint256 b) internal returns (uint256) {
268
        if(!(a > b)){
269
            uint256 value = b;
270
            string memory aStr = PropertiesLibString.toString(a);
271
            string memory valueStr = PropertiesLibString.toString(value);
272
            bytes memory message = abi.encodePacked("Clamping value ", aStr, " to ", valueStr);
273
            emit LogString(string(message));
274
            return value;
275
        }
276
        return a;
277
    }
278

                            
                        
279
    /// @notice int256 version of clampGte
280
    function clampGte(int256 a, int256 b) internal returns (int256) {
281
        if(!(a > b)){
282
            int256 value = b;
283
            string memory aStr = PropertiesLibString.toString(a);
284
            string memory valueStr = PropertiesLibString.toString(value);
285
            bytes memory message = abi.encodePacked("Clamping value ", aStr, " to ", valueStr);
286
            emit LogString(string(message));
287
            return value;
288
        }
289
        return a;
290
    }
291
}
292

                            
                        
293
/// @notice Efficient library for creating string representations of integers.
294
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/LibString.sol)
295
/// @author Modified from Solady (https://github.com/Vectorized/solady/blob/main/src/utils/LibString.sol)
296
/// @dev Name of the library is modified to prevent collisions with contract-under-test uses of LibString
297
library PropertiesLibString {
298

                            
                        
299
    function toString(int256 value) internal pure returns (string memory str) {
300
        uint256 absValue = value >= 0 ? uint256(value) : uint256(-value);
301
        str = toString(absValue);
302

                            
                        
303
        if(value < 0) {
304
            str = string(abi.encodePacked("-", str));
305
        }
306
    }
307

                            
                        
308
    function toString(uint256 value) internal pure returns (string memory str) {
309
        /// @solidity memory-safe-assembly
310
        assembly {
311
            // The maximum value of a uint256 contains 78 digits (1 byte per digit), but we allocate 160 bytes
312
            // to keep the free memory pointer word aligned. We'll need 1 word for the length, 1 word for the
313
            // trailing zeros padding, and 3 other words for a max of 78 digits. In total: 5 * 32 = 160 bytes.
314
            let newFreeMemoryPointer := add(mload(0x40), 160)
315

                            
                        
316
            // Update the free memory pointer to avoid overriding our string.
317
            mstore(0x40, newFreeMemoryPointer)
318

                            
                        
319
            // Assign str to the end of the zone of newly allocated memory.
320
            str := sub(newFreeMemoryPointer, 32)
321

                            
                        
322
            // Clean the last word of memory it may not be overwritten.
323
            mstore(str, 0)
324

                            
                        
325
            // Cache the end of the memory to calculate the length later.
326
            let end := str
327

                            
                        
328
            // We write the string from rightmost digit to leftmost digit.
329
            // The following is essentially a do-while loop that also handles the zero case.
330
            // prettier-ignore
331
            for { let temp := value } 1 {} {
332
                // Move the pointer 1 byte to the left.
333
                str := sub(str, 1)
334

                            
                        
335
                // Write the character to the pointer.
336
                // The ASCII index of the '0' character is 48.
337
                mstore8(str, add(48, mod(temp, 10)))
338

                            
                        
339
                // Keep dividing temp until zero.
340
                temp := div(temp, 10)
341

                            
                        
342
                 // prettier-ignore
343
                if iszero(temp) { break }
344
            }
345

                            
                        
346
            // Compute and cache the final total length of the string.
347
            let length := sub(end, str)
348

                            
                        
349
            // Move the pointer 32 bytes leftwards to make room for the length.
350
            str := sub(str, 32)
351

                            
                        
352
            // Store the string's length at the start of memory allocated for our string.
353
            mstore(str, length)
354
        }
355
    }
356

                            
                        
357
    function toString(address value) internal pure returns (string memory str){
358
        bytes memory s = new bytes(40);
359
        for (uint i = 0; i < 20; i++) {
360
            bytes1 b = bytes1(uint8(uint(uint160(value)) / (2**(8*(19 - i)))));
361
            bytes1 hi = bytes1(uint8(b) / 16);
362
            bytes1 lo = bytes1(uint8(b) - 16 * uint8(hi));
363
            s[2*i] = char(hi);
364
            s[2*i+1] = char(lo);            
365
        }
366
        return string(s);
367
    }
368

                            
                        
369
    function char(bytes1 b) internal pure returns (bytes1 c) {
370
        if (uint8(b) < 10) return bytes1(uint8(b) + 0x30);
371
        else return bytes1(uint8(b) + 0x57);
372
    }
373
}
374